blob: df3423dedd046578c8c3063e9fe19b6813347b59 [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
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Doug Zongkerb34fcce2014-09-11 09:34:56 -070033from rangelib import *
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Doug Zongker55d93282011-01-25 17:03:34 -080035try:
davidcad0bb92011-03-15 14:21:38 +000036 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037except ImportError:
davidcad0bb92011-03-15 14:21:38 +000038 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080039
Doug Zongkereef39442009-04-02 12:14:19 -070040# missing in Python 2.4 and before
41if not hasattr(os, "SEEK_SET"):
42 os.SEEK_SET = 0
43
44class Options(object): pass
45OPTIONS = Options()
Doug Zongker85448772014-09-09 14:59:20 -070046
47DEFAULT_SEARCH_PATH_BY_PLATFORM = {
48 "linux2": "out/host/linux-x86",
49 "darwin": "out/host/darwin-x86",
50 }
51OPTIONS.search_path = DEFAULT_SEARCH_PATH_BY_PLATFORM.get(sys.platform, None)
52
T.R. Fullhart37e10522013-03-18 10:31:26 -070053OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
54OPTIONS.extra_signapk_args = []
55OPTIONS.java_path = "java" # Use the one on the path by default.
Baligh Uddin339ee492014-09-05 11:18:07 -070056OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070057OPTIONS.public_key_suffix = ".x509.pem"
58OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070059OPTIONS.verbose = False
60OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070061OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080062OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070063OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070064
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080065
66# Values for "certificate" in apkcerts that mean special things.
67SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
68
69
Doug Zongkereef39442009-04-02 12:14:19 -070070class ExternalError(RuntimeError): pass
71
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Doug Zongkerc9253822014-02-04 12:17:58 -080097def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
102 if isinstance(input, zipfile.ZipFile):
103 return input.read(fn)
104 else:
105 path = os.path.join(input, *fn.split("/"))
106 try:
107 with open(path) as f:
108 return f.read()
109 except IOError, e:
110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800125 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700126 except KeyError:
127 # ok if flags don't exist
128 pass
129
130 if "recovery_api_version" not in d:
131 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800132 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700133 except KeyError:
134 raise ValueError("can't find recovery API version in input target-files")
135
136 if "tool_extensions" not in d:
137 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800138 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700139 except KeyError:
140 # ok if extensions don't exist
141 pass
142
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800143 if "fstab_version" not in d:
144 d["fstab_version"] = "1"
145
Doug Zongker37974732010-09-16 17:44:38 -0700146 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700148 for line in data.split("\n"):
149 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700150 name, value = line.split(" ", 1)
151 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700152 if name == "blocksize":
153 d[name] = value
154 else:
155 d[name + "_size"] = value
156 except KeyError:
157 pass
158
159 def makeint(key):
160 if key in d:
161 d[key] = int(d[key], 0)
162
163 makeint("recovery_api_version")
164 makeint("blocksize")
165 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700166 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700167 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700168 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700169 makeint("recovery_size")
170 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800171 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Doug Zongkerc9253822014-02-04 12:17:58 -0800173 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
174 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700175 return d
176
Doug Zongkerc9253822014-02-04 12:17:58 -0800177def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700178 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800179 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700180 except KeyError:
181 print "Warning: could not find SYSTEM/build.prop in %s" % zip
182 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700183 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184
Michael Runge6e836112014-04-15 17:40:21 -0700185def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700186 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700187 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188 line = line.strip()
189 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700190 if "=" in line:
191 name, value = line.split("=", 1)
192 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700193 return d
194
Doug Zongkerc9253822014-02-04 12:17:58 -0800195def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700196 class Partition(object):
197 pass
198
199 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800200 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700201 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800202 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700203 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800205 if fstab_version == 1:
206 d = {}
207 for line in data.split("\n"):
208 line = line.strip()
209 if not line or line.startswith("#"): continue
210 pieces = line.split()
211 if not (3 <= len(pieces) <= 4):
212 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700213
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800214 p = Partition()
215 p.mount_point = pieces[0]
216 p.fs_type = pieces[1]
217 p.device = pieces[2]
218 p.length = 0
219 options = None
220 if len(pieces) >= 4:
221 if pieces[3].startswith("/"):
222 p.device2 = pieces[3]
223 if len(pieces) >= 5:
224 options = pieces[4]
225 else:
226 p.device2 = None
227 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800228 else:
229 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700230
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 if options:
232 options = options.split(",")
233 for i in options:
234 if i.startswith("length="):
235 p.length = int(i[7:])
236 else:
237 print "%s: unknown option \"%s\"" % (p.mount_point, i)
238
239 d[p.mount_point] = p
240
241 elif fstab_version == 2:
242 d = {}
243 for line in data.split("\n"):
244 line = line.strip()
245 if not line or line.startswith("#"): continue
246 pieces = line.split()
247 if len(pieces) != 5:
248 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
249
250 # Ignore entries that are managed by vold
251 options = pieces[4]
252 if "voldmanaged=" in options: continue
253
254 # It's a good line, parse it
255 p = Partition()
256 p.device = pieces[0]
257 p.mount_point = pieces[1]
258 p.fs_type = pieces[2]
259 p.device2 = None
260 p.length = 0
261
Doug Zongker086cbb02011-02-17 15:54:20 -0800262 options = options.split(",")
263 for i in options:
264 if i.startswith("length="):
265 p.length = int(i[7:])
266 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 # Ignore all unknown options in the unified fstab
268 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800269
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270 d[p.mount_point] = p
271
272 else:
273 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
274
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700275 return d
276
277
Doug Zongker37974732010-09-16 17:44:38 -0700278def DumpInfoDict(d):
279 for k, v in sorted(d.items()):
280 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700281
Doug Zongkerd5131602012-08-02 14:46:42 -0700282def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700283 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700284 'sourcedir'), and turn them into a boot image. Return the image
285 data, or None if sourcedir does not appear to contains files for
286 building the requested image."""
287
288 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
289 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
290 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700291
Doug Zongkerd5131602012-08-02 14:46:42 -0700292 if info_dict is None:
293 info_dict = OPTIONS.info_dict
294
Doug Zongkereef39442009-04-02 12:14:19 -0700295 ramdisk_img = tempfile.NamedTemporaryFile()
296 img = tempfile.NamedTemporaryFile()
297
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700298 if os.access(fs_config_file, os.F_OK):
299 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
300 else:
301 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
302 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700303 p2 = Run(["minigzip"],
304 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700305
306 p2.wait()
307 p1.wait()
308 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700309 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700310
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800311 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
312 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
313
314 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700315
Benoit Fradina45a8682014-07-14 21:00:43 +0200316 fn = os.path.join(sourcedir, "second")
317 if os.access(fn, os.F_OK):
318 cmd.append("--second")
319 cmd.append(fn)
320
Doug Zongker171f1cd2009-06-15 22:36:37 -0700321 fn = os.path.join(sourcedir, "cmdline")
322 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700323 cmd.append("--cmdline")
324 cmd.append(open(fn).read().rstrip("\n"))
325
326 fn = os.path.join(sourcedir, "base")
327 if os.access(fn, os.F_OK):
328 cmd.append("--base")
329 cmd.append(open(fn).read().rstrip("\n"))
330
Ameya Thakurae0a9972012-10-24 19:31:42 -0700331 fn = os.path.join(sourcedir, "tagsaddr")
332 if os.access(fn, os.F_OK):
333 cmd.append("--tags-addr")
334 cmd.append(open(fn).read().rstrip("\n"))
335
336 fn = os.path.join(sourcedir, "ramdisk_offset")
337 if os.access(fn, os.F_OK):
338 cmd.append("--ramdisk_offset")
339 cmd.append(open(fn).read().rstrip("\n"))
340
341 fn = os.path.join(sourcedir, "dt_args")
342 if os.access(fn, os.F_OK):
343 cmd.append("--dt")
344 cmd.append(open(fn).read().rstrip("\n"))
345
Ying Wang4de6b5b2010-08-25 14:29:34 -0700346 fn = os.path.join(sourcedir, "pagesize")
347 if os.access(fn, os.F_OK):
348 cmd.append("--pagesize")
349 cmd.append(open(fn).read().rstrip("\n"))
350
Doug Zongkerd5131602012-08-02 14:46:42 -0700351 args = info_dict.get("mkbootimg_args", None)
352 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700353 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700354
Doug Zongker38a649f2009-06-17 09:07:09 -0700355 cmd.extend(["--ramdisk", ramdisk_img.name,
356 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700357 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700358 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700359 assert p.returncode == 0, "mkbootimg of %s image failed" % (
360 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700361
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700362 if info_dict.get("verity_key", None):
363 path = "/" + os.path.basename(sourcedir).lower()
Sami Tolvanen8d212ea2014-11-06 20:38:00 -0800364 cmd = ["boot_signer", path, img.name, info_dict["verity_key"] + ".pk8", info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700365 p = Run(cmd, stdout=subprocess.PIPE)
366 p.communicate()
367 assert p.returncode == 0, "boot_signer of %s image failed" % path
368
Doug Zongkereef39442009-04-02 12:14:19 -0700369 img.seek(os.SEEK_SET, 0)
370 data = img.read()
371
372 ramdisk_img.close()
373 img.close()
374
375 return data
376
377
Doug Zongkerd5131602012-08-02 14:46:42 -0700378def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
379 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800380 """Return a File object (with name 'name') with the desired bootable
381 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700382 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
383 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800384 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700385
Doug Zongker55d93282011-01-25 17:03:34 -0800386 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
387 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700388 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800389 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700390
391 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
392 if os.path.exists(prebuilt_path):
393 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
394 return File.FromLocalFile(name, prebuilt_path)
395
396 print "building image from target_files %s..." % (tree_subdir,)
397 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
398 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
399 os.path.join(unpack_dir, fs_config),
400 info_dict)
401 if data:
402 return File(name, data)
403 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800404
Doug Zongkereef39442009-04-02 12:14:19 -0700405
Doug Zongker75f17362009-12-08 13:46:44 -0800406def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800407 """Unzip the given archive into a temporary directory and return the name.
408
409 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
410 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
411
412 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
413 main file), open for reading.
414 """
Doug Zongkereef39442009-04-02 12:14:19 -0700415
416 tmp = tempfile.mkdtemp(prefix="targetfiles-")
417 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800418
419 def unzip_to_dir(filename, dirname):
420 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
421 if pattern is not None:
422 cmd.append(pattern)
423 p = Run(cmd, stdout=subprocess.PIPE)
424 p.communicate()
425 if p.returncode != 0:
426 raise ExternalError("failed to unzip input target-files \"%s\"" %
427 (filename,))
428
429 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
430 if m:
431 unzip_to_dir(m.group(1), tmp)
432 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
433 filename = m.group(1)
434 else:
435 unzip_to_dir(filename, tmp)
436
437 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700438
439
440def GetKeyPasswords(keylist):
441 """Given a list of keys, prompt the user to enter passwords for
442 those which require them. Return a {key: password} dict. password
443 will be None if the key has no password."""
444
Doug Zongker8ce7c252009-05-22 13:34:54 -0700445 no_passwords = []
446 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700447 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700448 devnull = open("/dev/null", "w+b")
449 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800450 # We don't need a password for things that aren't really keys.
451 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700452 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700453 continue
454
T.R. Fullhart37e10522013-03-18 10:31:26 -0700455 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700456 "-inform", "DER", "-nocrypt"],
457 stdin=devnull.fileno(),
458 stdout=devnull.fileno(),
459 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700460 p.communicate()
461 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700462 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700463 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700464 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700465 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
466 "-inform", "DER", "-passin", "pass:"],
467 stdin=devnull.fileno(),
468 stdout=devnull.fileno(),
469 stderr=subprocess.PIPE)
470 stdout, stderr = p.communicate()
471 if p.returncode == 0:
472 # Encrypted key with empty string as password.
473 key_passwords[k] = ''
474 elif stderr.startswith('Error decrypting key'):
475 # Definitely encrypted key.
476 # It would have said "Error reading key" if it didn't parse correctly.
477 need_passwords.append(k)
478 else:
479 # Potentially, a type of key that openssl doesn't understand.
480 # We'll let the routines in signapk.jar handle it.
481 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700482 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700483
T.R. Fullhart37e10522013-03-18 10:31:26 -0700484 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700485 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700486 return key_passwords
487
488
Doug Zongker951495f2009-08-14 12:44:19 -0700489def SignFile(input_name, output_name, key, password, align=None,
490 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700491 """Sign the input_name zip/jar/apk, producing output_name. Use the
492 given key and password (the latter may be None if the key does not
493 have a password.
494
495 If align is an integer > 1, zipalign is run to align stored files in
496 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700497
498 If whole_file is true, use the "-w" option to SignApk to embed a
499 signature that covers the whole file in the archive comment of the
500 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700501 """
Doug Zongker951495f2009-08-14 12:44:19 -0700502
Doug Zongkereef39442009-04-02 12:14:19 -0700503 if align == 0 or align == 1:
504 align = None
505
506 if align:
507 temp = tempfile.NamedTemporaryFile()
508 sign_name = temp.name
509 else:
510 sign_name = output_name
511
Baligh Uddin339ee492014-09-05 11:18:07 -0700512 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700513 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
514 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700515 if whole_file:
516 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700517 cmd.extend([key + OPTIONS.public_key_suffix,
518 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700519 input_name, sign_name])
520
521 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700522 if password is not None:
523 password += "\n"
524 p.communicate(password)
525 if p.returncode != 0:
526 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
527
528 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700529 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700530 p.communicate()
531 if p.returncode != 0:
532 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
533 temp.close()
534
535
Doug Zongker37974732010-09-16 17:44:38 -0700536def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700537 """Check the data string passed against the max size limit, if
538 any, for the given target. Raise exception if the data is too big.
539 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700540
Doug Zongker1684d9c2010-09-17 07:44:38 -0700541 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700542 mount_point = "/" + target
543
Ying Wangf8824af2014-06-03 14:07:27 -0700544 fs_type = None
545 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700546 if info_dict["fstab"]:
547 if mount_point == "/userdata": mount_point = "/data"
548 p = info_dict["fstab"][mount_point]
549 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800550 device = p.device
551 if "/" in device:
552 device = device[device.rfind("/")+1:]
553 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700554 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700555
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700556 if fs_type == "yaffs2":
557 # image size should be increased by 1/64th to account for the
558 # spare area (64 bytes per 2k page)
559 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800560 size = len(data)
561 pct = float(size) * 100.0 / limit
562 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
563 if pct >= 99.0:
564 raise ExternalError(msg)
565 elif pct >= 95.0:
566 print
567 print " WARNING: ", msg
568 print
569 elif OPTIONS.verbose:
570 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700571
572
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800573def ReadApkCerts(tf_zip):
574 """Given a target_files ZipFile, parse the META/apkcerts.txt file
575 and return a {package: cert} dict."""
576 certmap = {}
577 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
578 line = line.strip()
579 if not line: continue
580 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
581 r'private_key="(.*)"$', line)
582 if m:
583 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700584 public_key_suffix_len = len(OPTIONS.public_key_suffix)
585 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800586 if cert in SPECIAL_CERT_STRINGS and not privkey:
587 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700588 elif (cert.endswith(OPTIONS.public_key_suffix) and
589 privkey.endswith(OPTIONS.private_key_suffix) and
590 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
591 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800592 else:
593 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
594 return certmap
595
596
Doug Zongkereef39442009-04-02 12:14:19 -0700597COMMON_DOCSTRING = """
598 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700599 Prepend <dir>/bin to the list of places to search for binaries
600 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700601
Doug Zongker05d3dea2009-06-22 11:32:31 -0700602 -s (--device_specific) <file>
603 Path to the python module containing device-specific
604 releasetools code.
605
Doug Zongker8bec09e2009-11-30 15:37:14 -0800606 -x (--extra) <key=value>
607 Add a key/value pair to the 'extras' dict, which device-specific
608 extension code may look at.
609
Doug Zongkereef39442009-04-02 12:14:19 -0700610 -v (--verbose)
611 Show command lines being executed.
612
613 -h (--help)
614 Display this usage message and exit.
615"""
616
617def Usage(docstring):
618 print docstring.rstrip("\n")
619 print COMMON_DOCSTRING
620
621
622def ParseOptions(argv,
623 docstring,
624 extra_opts="", extra_long_opts=(),
625 extra_option_handler=None):
626 """Parse the options in argv and return any arguments that aren't
627 flags. docstring is the calling module's docstring, to be displayed
628 for errors and -h. extra_opts and extra_long_opts are for flags
629 defined by the caller, which are processed by passing them to
630 extra_option_handler."""
631
632 try:
633 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800634 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700635 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700636 "java_path=", "java_args=", "public_key_suffix=",
637 "private_key_suffix=", "device_specific=", "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700638 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700639 except getopt.GetoptError, err:
640 Usage(docstring)
641 print "**", str(err), "**"
642 sys.exit(2)
643
644 path_specified = False
645
646 for o, a in opts:
647 if o in ("-h", "--help"):
648 Usage(docstring)
649 sys.exit()
650 elif o in ("-v", "--verbose"):
651 OPTIONS.verbose = True
652 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700653 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700654 elif o in ("--signapk_path",):
655 OPTIONS.signapk_path = a
656 elif o in ("--extra_signapk_args",):
657 OPTIONS.extra_signapk_args = shlex.split(a)
658 elif o in ("--java_path",):
659 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700660 elif o in ("--java_args",):
661 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700662 elif o in ("--public_key_suffix",):
663 OPTIONS.public_key_suffix = a
664 elif o in ("--private_key_suffix",):
665 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700666 elif o in ("-s", "--device_specific"):
667 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800668 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800669 key, value = a.split("=", 1)
670 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700671 else:
672 if extra_option_handler is None or not extra_option_handler(o, a):
673 assert False, "unknown option \"%s\"" % (o,)
674
Doug Zongker85448772014-09-09 14:59:20 -0700675 if OPTIONS.search_path:
676 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
677 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700678
679 return args
680
681
Doug Zongkerfc44a512014-08-26 13:10:25 -0700682def MakeTempFile(prefix=None, suffix=None):
683 """Make a temp file and add it to the list of things to be deleted
684 when Cleanup() is called. Return the filename."""
685 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
686 os.close(fd)
687 OPTIONS.tempfiles.append(fn)
688 return fn
689
690
Doug Zongkereef39442009-04-02 12:14:19 -0700691def Cleanup():
692 for i in OPTIONS.tempfiles:
693 if os.path.isdir(i):
694 shutil.rmtree(i)
695 else:
696 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700697
698
699class PasswordManager(object):
700 def __init__(self):
701 self.editor = os.getenv("EDITOR", None)
702 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
703
704 def GetPasswords(self, items):
705 """Get passwords corresponding to each string in 'items',
706 returning a dict. (The dict may have keys in addition to the
707 values in 'items'.)
708
709 Uses the passwords in $ANDROID_PW_FILE if available, letting the
710 user edit that file to add more needed passwords. If no editor is
711 available, or $ANDROID_PW_FILE isn't define, prompts the user
712 interactively in the ordinary way.
713 """
714
715 current = self.ReadFile()
716
717 first = True
718 while True:
719 missing = []
720 for i in items:
721 if i not in current or not current[i]:
722 missing.append(i)
723 # Are all the passwords already in the file?
724 if not missing: return current
725
726 for i in missing:
727 current[i] = ""
728
729 if not first:
730 print "key file %s still missing some passwords." % (self.pwfile,)
731 answer = raw_input("try to edit again? [y]> ").strip()
732 if answer and answer[0] not in 'yY':
733 raise RuntimeError("key passwords unavailable")
734 first = False
735
736 current = self.UpdateAndReadFile(current)
737
738 def PromptResult(self, current):
739 """Prompt the user to enter a value (password) for each key in
740 'current' whose value is fales. Returns a new dict with all the
741 values.
742 """
743 result = {}
744 for k, v in sorted(current.iteritems()):
745 if v:
746 result[k] = v
747 else:
748 while True:
749 result[k] = getpass.getpass("Enter password for %s key> "
750 % (k,)).strip()
751 if result[k]: break
752 return result
753
754 def UpdateAndReadFile(self, current):
755 if not self.editor or not self.pwfile:
756 return self.PromptResult(current)
757
758 f = open(self.pwfile, "w")
759 os.chmod(self.pwfile, 0600)
760 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
761 f.write("# (Additional spaces are harmless.)\n\n")
762
763 first_line = None
764 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
765 sorted.sort()
766 for i, (_, k, v) in enumerate(sorted):
767 f.write("[[[ %s ]]] %s\n" % (v, k))
768 if not v and first_line is None:
769 # position cursor on first line with no password.
770 first_line = i + 4
771 f.close()
772
773 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
774 _, _ = p.communicate()
775
776 return self.ReadFile()
777
778 def ReadFile(self):
779 result = {}
780 if self.pwfile is None: return result
781 try:
782 f = open(self.pwfile, "r")
783 for line in f:
784 line = line.strip()
785 if not line or line[0] == '#': continue
786 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
787 if not m:
788 print "failed to parse password file: ", line
789 else:
790 result[m.group(2)] = m.group(1)
791 f.close()
792 except IOError, e:
793 if e.errno != errno.ENOENT:
794 print "error reading password file: ", str(e)
795 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700796
797
Geremy Condra36bd3652014-02-06 19:45:10 -0800798def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700799 # use a fixed timestamp so the output is repeatable.
800 zinfo = zipfile.ZipInfo(filename=filename,
801 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800802 if compression is None:
803 zinfo.compress_type = zip.compression
804 else:
805 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700806 zinfo.external_attr = perms << 16
807 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700808
809
810class DeviceSpecificParams(object):
811 module = None
812 def __init__(self, **kwargs):
813 """Keyword arguments to the constructor become attributes of this
814 object, which is passed to all functions in the device-specific
815 module."""
816 for k, v in kwargs.iteritems():
817 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800818 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700819
820 if self.module is None:
821 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700822 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700823 try:
824 if os.path.isdir(path):
825 info = imp.find_module("releasetools", [path])
826 else:
827 d, f = os.path.split(path)
828 b, x = os.path.splitext(f)
829 if x == ".py":
830 f = b
831 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800832 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700833 self.module = imp.load_module("device_specific", *info)
834 except ImportError:
835 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700836
837 def _DoCall(self, function_name, *args, **kwargs):
838 """Call the named function in the device-specific module, passing
839 the given args and kwargs. The first argument to the call will be
840 the DeviceSpecific object itself. If there is no module, or the
841 module does not define the function, return the value of the
842 'default' kwarg (which itself defaults to None)."""
843 if self.module is None or not hasattr(self.module, function_name):
844 return kwargs.get("default", None)
845 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
846
847 def FullOTA_Assertions(self):
848 """Called after emitting the block of assertions at the top of a
849 full OTA package. Implementations can add whatever additional
850 assertions they like."""
851 return self._DoCall("FullOTA_Assertions")
852
Doug Zongkere5ff5902012-01-17 10:55:37 -0800853 def FullOTA_InstallBegin(self):
854 """Called at the start of full OTA installation."""
855 return self._DoCall("FullOTA_InstallBegin")
856
Doug Zongker05d3dea2009-06-22 11:32:31 -0700857 def FullOTA_InstallEnd(self):
858 """Called at the end of full OTA installation; typically this is
859 used to install the image for the device's baseband processor."""
860 return self._DoCall("FullOTA_InstallEnd")
861
862 def IncrementalOTA_Assertions(self):
863 """Called after emitting the block of assertions at the top of an
864 incremental OTA package. Implementations can add whatever
865 additional assertions they like."""
866 return self._DoCall("IncrementalOTA_Assertions")
867
Doug Zongkere5ff5902012-01-17 10:55:37 -0800868 def IncrementalOTA_VerifyBegin(self):
869 """Called at the start of the verification phase of incremental
870 OTA installation; additional checks can be placed here to abort
871 the script before any changes are made."""
872 return self._DoCall("IncrementalOTA_VerifyBegin")
873
Doug Zongker05d3dea2009-06-22 11:32:31 -0700874 def IncrementalOTA_VerifyEnd(self):
875 """Called at the end of the verification phase of incremental OTA
876 installation; additional checks can be placed here to abort the
877 script before any changes are made."""
878 return self._DoCall("IncrementalOTA_VerifyEnd")
879
Doug Zongkere5ff5902012-01-17 10:55:37 -0800880 def IncrementalOTA_InstallBegin(self):
881 """Called at the start of incremental OTA installation (after
882 verification is complete)."""
883 return self._DoCall("IncrementalOTA_InstallBegin")
884
Doug Zongker05d3dea2009-06-22 11:32:31 -0700885 def IncrementalOTA_InstallEnd(self):
886 """Called at the end of incremental OTA installation; typically
887 this is used to install the image for the device's baseband
888 processor."""
889 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700890
891class File(object):
892 def __init__(self, name, data):
893 self.name = name
894 self.data = data
895 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800896 self.sha1 = sha1(data).hexdigest()
897
898 @classmethod
899 def FromLocalFile(cls, name, diskname):
900 f = open(diskname, "rb")
901 data = f.read()
902 f.close()
903 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700904
905 def WriteToTemp(self):
906 t = tempfile.NamedTemporaryFile()
907 t.write(self.data)
908 t.flush()
909 return t
910
Geremy Condra36bd3652014-02-06 19:45:10 -0800911 def AddToZip(self, z, compression=None):
912 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700913
914DIFF_PROGRAM_BY_EXT = {
915 ".gz" : "imgdiff",
916 ".zip" : ["imgdiff", "-z"],
917 ".jar" : ["imgdiff", "-z"],
918 ".apk" : ["imgdiff", "-z"],
919 ".img" : "imgdiff",
920 }
921
922class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700923 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700924 self.tf = tf
925 self.sf = sf
926 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700927 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700928
929 def ComputePatch(self):
930 """Compute the patch (as a string of data) needed to turn sf into
931 tf. Returns the same tuple as GetPatch()."""
932
933 tf = self.tf
934 sf = self.sf
935
Doug Zongker24cd2802012-08-14 16:36:15 -0700936 if self.diff_program:
937 diff_program = self.diff_program
938 else:
939 ext = os.path.splitext(tf.name)[1]
940 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700941
942 ttemp = tf.WriteToTemp()
943 stemp = sf.WriteToTemp()
944
945 ext = os.path.splitext(tf.name)[1]
946
947 try:
948 ptemp = tempfile.NamedTemporaryFile()
949 if isinstance(diff_program, list):
950 cmd = copy.copy(diff_program)
951 else:
952 cmd = [diff_program]
953 cmd.append(stemp.name)
954 cmd.append(ttemp.name)
955 cmd.append(ptemp.name)
956 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700957 err = []
958 def run():
959 _, e = p.communicate()
960 if e: err.append(e)
961 th = threading.Thread(target=run)
962 th.start()
963 th.join(timeout=300) # 5 mins
964 if th.is_alive():
965 print "WARNING: diff command timed out"
966 p.terminate()
967 th.join(5)
968 if th.is_alive():
969 p.kill()
970 th.join()
971
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700972 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700973 print "WARNING: failure running %s:\n%s\n" % (
974 diff_program, "".join(err))
975 self.patch = None
976 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700977 diff = ptemp.read()
978 finally:
979 ptemp.close()
980 stemp.close()
981 ttemp.close()
982
983 self.patch = diff
984 return self.tf, self.sf, self.patch
985
986
987 def GetPatch(self):
988 """Return a tuple (target_file, source_file, patch_data).
989 patch_data may be None if ComputePatch hasn't been called, or if
990 computing the patch failed."""
991 return self.tf, self.sf, self.patch
992
993
994def ComputeDifferences(diffs):
995 """Call ComputePatch on all the Difference objects in 'diffs'."""
996 print len(diffs), "diffs to compute"
997
998 # Do the largest files first, to try and reduce the long-pole effect.
999 by_size = [(i.tf.size, i) for i in diffs]
1000 by_size.sort(reverse=True)
1001 by_size = [i[1] for i in by_size]
1002
1003 lock = threading.Lock()
1004 diff_iter = iter(by_size) # accessed under lock
1005
1006 def worker():
1007 try:
1008 lock.acquire()
1009 for d in diff_iter:
1010 lock.release()
1011 start = time.time()
1012 d.ComputePatch()
1013 dur = time.time() - start
1014 lock.acquire()
1015
1016 tf, sf, patch = d.GetPatch()
1017 if sf.name == tf.name:
1018 name = tf.name
1019 else:
1020 name = "%s (%s)" % (tf.name, sf.name)
1021 if patch is None:
1022 print "patching failed! %s" % (name,)
1023 else:
1024 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1025 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1026 lock.release()
1027 except Exception, e:
1028 print e
1029 raise
1030
1031 # start worker threads; wait for them all to finish.
1032 threads = [threading.Thread(target=worker)
1033 for i in range(OPTIONS.worker_threads)]
1034 for th in threads:
1035 th.start()
1036 while threads:
1037 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001038
1039
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001040class BlockDifference:
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001041 def __init__(self, partition, tgt, src=None, check_first_block=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001042 self.tgt = tgt
1043 self.src = src
1044 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001045 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001046
Doug Zongker62338182014-09-08 08:29:55 -07001047 version = 1
1048 if OPTIONS.info_dict:
1049 version = max(
1050 int(i) for i in
1051 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1052
1053 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
1054 version=version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001055 tmpdir = tempfile.mkdtemp()
1056 OPTIONS.tempfiles.append(tmpdir)
1057 self.path = os.path.join(tmpdir, partition)
1058 b.Compute(self.path)
1059
1060 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1061
1062 def WriteScript(self, script, output_zip, progress=None):
1063 if not self.src:
1064 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001065 script.Print("Patching %s image unconditionally..." % (self.partition,))
1066 else:
1067 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001068
Jesse Zhao75bcea02015-01-06 10:59:53 -08001069 if progress: script.ShowProgress(progress, 0)
1070 self._WriteUpdate(script, output_zip)
1071
1072 def WriteVerifyScript(self, script):
1073 if not self.src:
1074 script.Print("Image %s will be patched unconditionally." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001075 else:
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001076 if self.check_first_block:
1077 self._CheckFirstBlock(script)
1078
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001079 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' %
1080 (self.device, self.src.care_map.to_string_raw(),
1081 self.src.TotalSha1()))
Jesse Zhao75bcea02015-01-06 10:59:53 -08001082 script.Print("Verified %s image..." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001083 script.AppendExtra(('else\n'
1084 ' (range_sha1("%s", "%s") == "%s") ||\n'
1085 ' abort("%s partition has unexpected contents");\n'
1086 'endif;') %
1087 (self.device, self.tgt.care_map.to_string_raw(),
1088 self.tgt.TotalSha1(), self.partition))
1089
1090 def _WriteUpdate(self, script, output_zip):
1091 partition = self.partition
1092 with open(self.path + ".transfer.list", "rb") as f:
1093 ZipWriteStr(output_zip, partition + ".transfer.list", f.read())
1094 with open(self.path + ".new.dat", "rb") as f:
1095 ZipWriteStr(output_zip, partition + ".new.dat", f.read())
1096 with open(self.path + ".patch.dat", "rb") as f:
1097 ZipWriteStr(output_zip, partition + ".patch.dat", f.read(),
1098 compression=zipfile.ZIP_STORED)
1099
1100 call = (('block_image_update("%s", '
1101 'package_extract_file("%s.transfer.list"), '
1102 '"%s.new.dat", "%s.patch.dat");\n') %
1103 (self.device, partition, partition, partition))
1104 script.AppendExtra(script._WordWrap(call))
1105
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001106 def _CheckFirstBlock(self, script):
1107 r = RangeSet((0, 1))
1108 h = sha1()
1109 for data in self.src.ReadRangeSet(r):
1110 h.update(data)
1111 h = h.hexdigest()
1112
1113 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1114 'abort("%s has been remounted R/W; '
1115 'reflash device to reenable OTA updates");')
1116 % (self.device, r.to_string_raw(), h, self.device))
1117
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001118
1119DataImage = blockimgdiff.DataImage
1120
1121
Doug Zongker96a57e72010-09-26 14:57:41 -07001122# map recovery.fstab's fs_types to mount/format "partition types"
1123PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001124 "ext4": "EMMC", "emmc": "EMMC",
1125 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001126
1127def GetTypeAndDevice(mount_point, info):
1128 fstab = info["fstab"]
1129 if fstab:
1130 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1131 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001132 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001133
1134
1135def ParseCertificate(data):
1136 """Parse a PEM-format certificate."""
1137 cert = []
1138 save = False
1139 for line in data.split("\n"):
1140 if "--END CERTIFICATE--" in line:
1141 break
1142 if save:
1143 cert.append(line)
1144 if "--BEGIN CERTIFICATE--" in line:
1145 save = True
1146 cert = "".join(cert).decode('base64')
1147 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001148
Doug Zongker412c02f2014-02-13 10:58:24 -08001149def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1150 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001151 """Generate a binary patch that creates the recovery image starting
1152 with the boot image. (Most of the space in these images is just the
1153 kernel, which is identical for the two, so the resulting patch
1154 should be efficient.) Add it to the output zip, along with a shell
1155 script that is run from init.rc on first boot to actually do the
1156 patching and install the new recovery image.
1157
1158 recovery_img and boot_img should be File objects for the
1159 corresponding images. info should be the dictionary returned by
1160 common.LoadInfoDict() on the input target_files.
1161 """
1162
Doug Zongker412c02f2014-02-13 10:58:24 -08001163 if info_dict is None:
1164 info_dict = OPTIONS.info_dict
1165
Doug Zongkerc9253822014-02-04 12:17:58 -08001166 diff_program = ["imgdiff"]
1167 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1168 if os.path.exists(path):
1169 diff_program.append("-b")
1170 diff_program.append(path)
1171 bonus_args = "-b /system/etc/recovery-resource.dat"
1172 else:
1173 bonus_args = ""
1174
1175 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1176 _, _, patch = d.ComputePatch()
1177 output_sink("recovery-from-boot.p", patch)
1178
Ying Wanga961a092014-07-29 11:42:37 -07001179 td_pair = GetTypeAndDevice("/boot", info_dict)
1180 if not td_pair:
1181 return
1182 boot_type, boot_device = td_pair
1183 td_pair = GetTypeAndDevice("/recovery", info_dict)
1184 if not td_pair:
1185 return
1186 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001187
1188 sh = """#!/system/bin/sh
1189if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1190 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1191else
1192 log -t recovery "Recovery image already installed"
1193fi
1194""" % { 'boot_size': boot_img.size,
1195 'boot_sha1': boot_img.sha1,
1196 'recovery_size': recovery_img.size,
1197 'recovery_sha1': recovery_img.sha1,
1198 'boot_type': boot_type,
1199 'boot_device': boot_device,
1200 'recovery_type': recovery_type,
1201 'recovery_device': recovery_device,
1202 'bonus_args': bonus_args,
1203 }
1204
1205 # The install script location moved from /system/etc to /system/bin
1206 # in the L release. Parse the init.rc file to find out where the
1207 # target-files expects it to be, and put it there.
1208 sh_location = "etc/install-recovery.sh"
1209 try:
1210 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1211 for line in f:
1212 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1213 if m:
1214 sh_location = m.group(1)
1215 print "putting script in", sh_location
1216 break
1217 except (OSError, IOError), e:
1218 print "failed to read init.rc: %s" % (e,)
1219
1220 output_sink(sh_location, sh)