blob: d5c15f5d8e8776481482497851adaf539cbfdb71 [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
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Baof3282b42015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
Tao Baoe09359a2015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
Tao Bao116977c2015-08-07 19:49:45 -070065 # Stash size cannot exceed cache_size * threshold.
66 self.cache_size = None
67 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070068
69
70OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070071
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080072
73# Values for "certificate" in apkcerts that mean special things.
74SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
75
76
Dan Albert8b72aef2015-03-23 19:13:21 -070077class ExternalError(RuntimeError):
78 pass
Doug Zongkereef39442009-04-02 12:14:19 -070079
80
81def Run(args, **kwargs):
82 """Create and return a subprocess.Popen object, printing the command
83 line on the terminal if -v was specified."""
84 if OPTIONS.verbose:
85 print " running: ", " ".join(args)
86 return subprocess.Popen(args, **kwargs)
87
88
Ying Wang7e6d4e42010-12-13 16:25:36 -080089def CloseInheritedPipes():
90 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
91 before doing other work."""
92 if platform.system() != "Darwin":
93 return
94 for d in range(3, 1025):
95 try:
96 stat = os.fstat(d)
97 if stat is not None:
98 pipebit = stat[0] & 0x1000
99 if pipebit != 0:
100 os.close(d)
101 except OSError:
102 pass
103
104
Dan Albert8b72aef2015-03-23 19:13:21 -0700105def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700106 """Read and parse the META/misc_info.txt key/value pairs from the
107 input target files and return a dict."""
108
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700110 if isinstance(input_file, zipfile.ZipFile):
111 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800112 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700113 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800114 try:
115 with open(path) as f:
116 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700117 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800118 if e.errno == errno.ENOENT:
119 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700120 d = {}
121 try:
Michael Runge6e836112014-04-15 17:40:21 -0700122 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700123 except KeyError:
124 # ok if misc_info.txt doesn't exist
125 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700126
Doug Zongker37974732010-09-16 17:44:38 -0700127 # backwards compatibility: These values used to be in their own
128 # files. Look for them, in case we're processing an old
129 # target_files zip.
130
131 if "mkyaffs2_extra_flags" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["mkyaffs2_extra_flags"] = read_helper(
134 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 # ok if flags don't exist
137 pass
138
139 if "recovery_api_version" not in d:
140 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700141 d["recovery_api_version"] = read_helper(
142 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700143 except KeyError:
144 raise ValueError("can't find recovery API version in input target-files")
145
146 if "tool_extensions" not in d:
147 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700149 except KeyError:
150 # ok if extensions don't exist
151 pass
152
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800153 if "fstab_version" not in d:
154 d["fstab_version"] = "1"
155
Doug Zongker37974732010-09-16 17:44:38 -0700156 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800157 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700158 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700159 if not line:
160 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700161 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700162 if not value:
163 continue
Doug Zongker37974732010-09-16 17:44:38 -0700164 if name == "blocksize":
165 d[name] = value
166 else:
167 d[name + "_size"] = value
168 except KeyError:
169 pass
170
171 def makeint(key):
172 if key in d:
173 d[key] = int(d[key], 0)
174
175 makeint("recovery_api_version")
176 makeint("blocksize")
177 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700178 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700179 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700180 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700181 makeint("recovery_size")
182 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800183 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700184
Tao Baob11d2c52015-07-21 18:01:20 -0700185 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
186 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800187 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188 return d
189
Doug Zongkerc9253822014-02-04 12:17:58 -0800190def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800192 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 except KeyError:
194 print "Warning: could not find SYSTEM/build.prop in %s" % zip
195 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700196 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700197
Michael Runge6e836112014-04-15 17:40:21 -0700198def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700199 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700200 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700201 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700202 if not line or line.startswith("#"):
203 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700204 if "=" in line:
205 name, value = line.split("=", 1)
206 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700207 return d
208
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700209def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700210 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700211 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700212 self.mount_point = mount_point
213 self.fs_type = fs_type
214 self.device = device
215 self.length = length
216 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700217 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218
219 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800220 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700221 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800222 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700223 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700224
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 if fstab_version == 1:
226 d = {}
227 for line in data.split("\n"):
228 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700229 if not line or line.startswith("#"):
230 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700232 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800234 options = None
235 if len(pieces) >= 4:
236 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 if len(pieces) >= 5:
239 options = pieces[4]
240 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700241 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800243 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700245
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 mount_point = pieces[0]
247 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800248 if options:
249 options = options.split(",")
250 for i in options:
251 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
257 device=pieces[2], length=length,
258 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259
260 elif fstab_version == 2:
261 d = {}
262 for line in data.split("\n"):
263 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 if not line or line.startswith("#"):
265 continue
Tao Bao548eb762015-06-10 12:32:41 -0700266 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 pieces = line.split()
268 if len(pieces) != 5:
269 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
270
271 # Ignore entries that are managed by vold
272 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 if "voldmanaged=" in options:
274 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275
276 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800278 options = options.split(",")
279 for i in options:
280 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800282 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800283 # Ignore all unknown options in the unified fstab
284 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800285
Tao Bao548eb762015-06-10 12:32:41 -0700286 mount_flags = pieces[3]
287 # Honor the SELinux context if present.
288 context = None
289 for i in mount_flags.split(","):
290 if i.startswith("context="):
291 context = i
292
Dan Albert8b72aef2015-03-23 19:13:21 -0700293 mount_point = pieces[1]
294 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700295 device=pieces[0], length=length,
296 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800297
298 else:
299 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
300
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700301 # / is used for the system mount point when the root directory is included in
Tao Baob11d2c52015-07-21 18:01:20 -0700302 # system. Other areas assume system is always at "/system" so point /system
303 # at /.
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700304 if system_root_image:
305 assert not d.has_key("/system") and d.has_key("/")
306 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700307 return d
308
309
Doug Zongker37974732010-09-16 17:44:38 -0700310def DumpInfoDict(d):
311 for k, v in sorted(d.items()):
312 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700313
Dan Albert8b72aef2015-03-23 19:13:21 -0700314
Tao Baob11d2c52015-07-21 18:01:20 -0700315def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
316 has_ramdisk=False):
317 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700318
Tao Baob11d2c52015-07-21 18:01:20 -0700319 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
320 'sourcedir'), and turn them into a boot image. Return the image data, or
321 None if sourcedir does not appear to contains files for building the
322 requested image."""
323
324 def make_ramdisk():
325 ramdisk_img = tempfile.NamedTemporaryFile()
326
327 if os.access(fs_config_file, os.F_OK):
328 cmd = ["mkbootfs", "-f", fs_config_file,
329 os.path.join(sourcedir, "RAMDISK")]
330 else:
331 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
332 p1 = Run(cmd, stdout=subprocess.PIPE)
333 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
334
335 p2.wait()
336 p1.wait()
337 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
338 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
339
340 return ramdisk_img
341
342 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
343 return None
344
345 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700346 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700347
Doug Zongkerd5131602012-08-02 14:46:42 -0700348 if info_dict is None:
349 info_dict = OPTIONS.info_dict
350
Doug Zongkereef39442009-04-02 12:14:19 -0700351 img = tempfile.NamedTemporaryFile()
352
Tao Baob11d2c52015-07-21 18:01:20 -0700353 if has_ramdisk:
354 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700355
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800356 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
357 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
358
359 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700360
Benoit Fradina45a8682014-07-14 21:00:43 +0200361 fn = os.path.join(sourcedir, "second")
362 if os.access(fn, os.F_OK):
363 cmd.append("--second")
364 cmd.append(fn)
365
Doug Zongker171f1cd2009-06-15 22:36:37 -0700366 fn = os.path.join(sourcedir, "cmdline")
367 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700368 cmd.append("--cmdline")
369 cmd.append(open(fn).read().rstrip("\n"))
370
371 fn = os.path.join(sourcedir, "base")
372 if os.access(fn, os.F_OK):
373 cmd.append("--base")
374 cmd.append(open(fn).read().rstrip("\n"))
375
Ying Wang4de6b5b2010-08-25 14:29:34 -0700376 fn = os.path.join(sourcedir, "pagesize")
377 if os.access(fn, os.F_OK):
378 cmd.append("--pagesize")
379 cmd.append(open(fn).read().rstrip("\n"))
380
Doug Zongkerd5131602012-08-02 14:46:42 -0700381 args = info_dict.get("mkbootimg_args", None)
382 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700383 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700384
Tao Baob11d2c52015-07-21 18:01:20 -0700385 if has_ramdisk:
386 cmd.extend(["--ramdisk", ramdisk_img.name])
387
Tao Baod95e9fd2015-03-29 23:07:41 -0700388 img_unsigned = None
389 if info_dict.get("vboot", None):
390 img_unsigned = tempfile.NamedTemporaryFile()
Tao Baob11d2c52015-07-21 18:01:20 -0700391 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700392 else:
Tao Baob11d2c52015-07-21 18:01:20 -0700393 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700394
395 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700396 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 assert p.returncode == 0, "mkbootimg of %s image failed" % (
398 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100400 if (info_dict.get("boot_signer", None) == "true" and
401 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700402 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700403 cmd = [OPTIONS.boot_signer_path]
404 cmd.extend(OPTIONS.boot_signer_args)
405 cmd.extend([path, img.name,
406 info_dict["verity_key"] + ".pk8",
407 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700408 p = Run(cmd, stdout=subprocess.PIPE)
409 p.communicate()
410 assert p.returncode == 0, "boot_signer of %s image failed" % path
411
Tao Baod95e9fd2015-03-29 23:07:41 -0700412 # Sign the image if vboot is non-empty.
413 elif info_dict.get("vboot", None):
414 path = "/" + os.path.basename(sourcedir).lower()
415 img_keyblock = tempfile.NamedTemporaryFile()
416 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
417 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700418 info_dict["vboot_key"] + ".vbprivk",
419 info_dict["vboot_subkey"] + ".vbprivk",
420 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700421 img.name]
422 p = Run(cmd, stdout=subprocess.PIPE)
423 p.communicate()
424 assert p.returncode == 0, "vboot_signer of %s image failed" % path
425
Tao Baof3282b42015-04-01 11:21:55 -0700426 # Clean up the temp files.
427 img_unsigned.close()
428 img_keyblock.close()
429
Doug Zongkereef39442009-04-02 12:14:19 -0700430 img.seek(os.SEEK_SET, 0)
431 data = img.read()
432
Tao Baob11d2c52015-07-21 18:01:20 -0700433 if has_ramdisk:
434 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700435 img.close()
436
437 return data
438
439
Doug Zongkerd5131602012-08-02 14:46:42 -0700440def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
441 info_dict=None):
Tao Baob11d2c52015-07-21 18:01:20 -0700442 """Return a File object with the desired bootable image.
443
444 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
445 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
446 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700447
Doug Zongker55d93282011-01-25 17:03:34 -0800448 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
449 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700450 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800451 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700452
453 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
454 if os.path.exists(prebuilt_path):
455 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
456 return File.FromLocalFile(name, prebuilt_path)
457
458 print "building image from target_files %s..." % (tree_subdir,)
Tao Baob11d2c52015-07-21 18:01:20 -0700459
460 if info_dict is None:
461 info_dict = OPTIONS.info_dict
462
463 # With system_root_image == "true", we don't pack ramdisk into the boot image.
464 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
465 prebuilt_name != "boot.img")
466
Doug Zongker6f1d0312014-08-22 08:07:12 -0700467 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Baob11d2c52015-07-21 18:01:20 -0700468 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
469 os.path.join(unpack_dir, fs_config),
470 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700471 if data:
472 return File(name, data)
473 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800474
Doug Zongkereef39442009-04-02 12:14:19 -0700475
Doug Zongker75f17362009-12-08 13:46:44 -0800476def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800477 """Unzip the given archive into a temporary directory and return the name.
478
479 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
480 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
481
482 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
483 main file), open for reading.
484 """
Doug Zongkereef39442009-04-02 12:14:19 -0700485
486 tmp = tempfile.mkdtemp(prefix="targetfiles-")
487 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800488
489 def unzip_to_dir(filename, dirname):
490 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
491 if pattern is not None:
492 cmd.append(pattern)
493 p = Run(cmd, stdout=subprocess.PIPE)
494 p.communicate()
495 if p.returncode != 0:
496 raise ExternalError("failed to unzip input target-files \"%s\"" %
497 (filename,))
498
499 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
500 if m:
501 unzip_to_dir(m.group(1), tmp)
502 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
503 filename = m.group(1)
504 else:
505 unzip_to_dir(filename, tmp)
506
507 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700508
509
510def GetKeyPasswords(keylist):
511 """Given a list of keys, prompt the user to enter passwords for
512 those which require them. Return a {key: password} dict. password
513 will be None if the key has no password."""
514
Doug Zongker8ce7c252009-05-22 13:34:54 -0700515 no_passwords = []
516 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700517 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700518 devnull = open("/dev/null", "w+b")
519 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800520 # We don't need a password for things that aren't really keys.
521 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700522 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700523 continue
524
T.R. Fullhart37e10522013-03-18 10:31:26 -0700525 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700526 "-inform", "DER", "-nocrypt"],
527 stdin=devnull.fileno(),
528 stdout=devnull.fileno(),
529 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700530 p.communicate()
531 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700532 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700533 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700534 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700535 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
536 "-inform", "DER", "-passin", "pass:"],
537 stdin=devnull.fileno(),
538 stdout=devnull.fileno(),
539 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700540 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700541 if p.returncode == 0:
542 # Encrypted key with empty string as password.
543 key_passwords[k] = ''
544 elif stderr.startswith('Error decrypting key'):
545 # Definitely encrypted key.
546 # It would have said "Error reading key" if it didn't parse correctly.
547 need_passwords.append(k)
548 else:
549 # Potentially, a type of key that openssl doesn't understand.
550 # We'll let the routines in signapk.jar handle it.
551 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700552 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700553
T.R. Fullhart37e10522013-03-18 10:31:26 -0700554 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700555 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700556 return key_passwords
557
558
Doug Zongker951495f2009-08-14 12:44:19 -0700559def SignFile(input_name, output_name, key, password, align=None,
560 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700561 """Sign the input_name zip/jar/apk, producing output_name. Use the
562 given key and password (the latter may be None if the key does not
563 have a password.
564
565 If align is an integer > 1, zipalign is run to align stored files in
566 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700567
568 If whole_file is true, use the "-w" option to SignApk to embed a
569 signature that covers the whole file in the archive comment of the
570 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700571 """
Doug Zongker951495f2009-08-14 12:44:19 -0700572
Doug Zongkereef39442009-04-02 12:14:19 -0700573 if align == 0 or align == 1:
574 align = None
575
576 if align:
577 temp = tempfile.NamedTemporaryFile()
578 sign_name = temp.name
579 else:
580 sign_name = output_name
581
Baligh Uddin339ee492014-09-05 11:18:07 -0700582 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700583 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
584 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700585 if whole_file:
586 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700587 cmd.extend([key + OPTIONS.public_key_suffix,
588 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700589 input_name, sign_name])
590
591 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700592 if password is not None:
593 password += "\n"
594 p.communicate(password)
595 if p.returncode != 0:
596 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
597
598 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700599 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700600 p.communicate()
601 if p.returncode != 0:
602 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
603 temp.close()
604
605
Doug Zongker37974732010-09-16 17:44:38 -0700606def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700607 """Check the data string passed against the max size limit, if
608 any, for the given target. Raise exception if the data is too big.
609 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700610
Dan Albert8b72aef2015-03-23 19:13:21 -0700611 if target.endswith(".img"):
612 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700613 mount_point = "/" + target
614
Ying Wangf8824af2014-06-03 14:07:27 -0700615 fs_type = None
616 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700617 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700618 if mount_point == "/userdata":
619 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700620 p = info_dict["fstab"][mount_point]
621 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800622 device = p.device
623 if "/" in device:
624 device = device[device.rfind("/")+1:]
625 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700626 if not fs_type or not limit:
627 return
Doug Zongkereef39442009-04-02 12:14:19 -0700628
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700629 if fs_type == "yaffs2":
630 # image size should be increased by 1/64th to account for the
631 # spare area (64 bytes per 2k page)
632 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800633 size = len(data)
634 pct = float(size) * 100.0 / limit
635 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
636 if pct >= 99.0:
637 raise ExternalError(msg)
638 elif pct >= 95.0:
639 print
640 print " WARNING: ", msg
641 print
642 elif OPTIONS.verbose:
643 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700644
645
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800646def ReadApkCerts(tf_zip):
647 """Given a target_files ZipFile, parse the META/apkcerts.txt file
648 and return a {package: cert} dict."""
649 certmap = {}
650 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
651 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700652 if not line:
653 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800654 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
655 r'private_key="(.*)"$', line)
656 if m:
657 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700658 public_key_suffix_len = len(OPTIONS.public_key_suffix)
659 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800660 if cert in SPECIAL_CERT_STRINGS and not privkey:
661 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700662 elif (cert.endswith(OPTIONS.public_key_suffix) and
663 privkey.endswith(OPTIONS.private_key_suffix) and
664 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
665 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800666 else:
667 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
668 return certmap
669
670
Doug Zongkereef39442009-04-02 12:14:19 -0700671COMMON_DOCSTRING = """
672 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700673 Prepend <dir>/bin to the list of places to search for binaries
674 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700675
Doug Zongker05d3dea2009-06-22 11:32:31 -0700676 -s (--device_specific) <file>
677 Path to the python module containing device-specific
678 releasetools code.
679
Doug Zongker8bec09e2009-11-30 15:37:14 -0800680 -x (--extra) <key=value>
681 Add a key/value pair to the 'extras' dict, which device-specific
682 extension code may look at.
683
Doug Zongkereef39442009-04-02 12:14:19 -0700684 -v (--verbose)
685 Show command lines being executed.
686
687 -h (--help)
688 Display this usage message and exit.
689"""
690
691def Usage(docstring):
692 print docstring.rstrip("\n")
693 print COMMON_DOCSTRING
694
695
696def ParseOptions(argv,
697 docstring,
698 extra_opts="", extra_long_opts=(),
699 extra_option_handler=None):
700 """Parse the options in argv and return any arguments that aren't
701 flags. docstring is the calling module's docstring, to be displayed
702 for errors and -h. extra_opts and extra_long_opts are for flags
703 defined by the caller, which are processed by passing them to
704 extra_option_handler."""
705
706 try:
707 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800708 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700709 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700710 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700711 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
712 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800713 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700714 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700715 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700716 Usage(docstring)
717 print "**", str(err), "**"
718 sys.exit(2)
719
Doug Zongkereef39442009-04-02 12:14:19 -0700720 for o, a in opts:
721 if o in ("-h", "--help"):
722 Usage(docstring)
723 sys.exit()
724 elif o in ("-v", "--verbose"):
725 OPTIONS.verbose = True
726 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700727 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700728 elif o in ("--signapk_path",):
729 OPTIONS.signapk_path = a
730 elif o in ("--extra_signapk_args",):
731 OPTIONS.extra_signapk_args = shlex.split(a)
732 elif o in ("--java_path",):
733 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700734 elif o in ("--java_args",):
735 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700736 elif o in ("--public_key_suffix",):
737 OPTIONS.public_key_suffix = a
738 elif o in ("--private_key_suffix",):
739 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800740 elif o in ("--boot_signer_path",):
741 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700742 elif o in ("--boot_signer_args",):
743 OPTIONS.boot_signer_args = shlex.split(a)
744 elif o in ("--verity_signer_path",):
745 OPTIONS.verity_signer_path = a
746 elif o in ("--verity_signer_args",):
747 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700748 elif o in ("-s", "--device_specific"):
749 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800750 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800751 key, value = a.split("=", 1)
752 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700753 else:
754 if extra_option_handler is None or not extra_option_handler(o, a):
755 assert False, "unknown option \"%s\"" % (o,)
756
Doug Zongker85448772014-09-09 14:59:20 -0700757 if OPTIONS.search_path:
758 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
759 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700760
761 return args
762
763
Doug Zongkerfc44a512014-08-26 13:10:25 -0700764def MakeTempFile(prefix=None, suffix=None):
765 """Make a temp file and add it to the list of things to be deleted
766 when Cleanup() is called. Return the filename."""
767 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
768 os.close(fd)
769 OPTIONS.tempfiles.append(fn)
770 return fn
771
772
Doug Zongkereef39442009-04-02 12:14:19 -0700773def Cleanup():
774 for i in OPTIONS.tempfiles:
775 if os.path.isdir(i):
776 shutil.rmtree(i)
777 else:
778 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700779
780
781class PasswordManager(object):
782 def __init__(self):
783 self.editor = os.getenv("EDITOR", None)
784 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
785
786 def GetPasswords(self, items):
787 """Get passwords corresponding to each string in 'items',
788 returning a dict. (The dict may have keys in addition to the
789 values in 'items'.)
790
791 Uses the passwords in $ANDROID_PW_FILE if available, letting the
792 user edit that file to add more needed passwords. If no editor is
793 available, or $ANDROID_PW_FILE isn't define, prompts the user
794 interactively in the ordinary way.
795 """
796
797 current = self.ReadFile()
798
799 first = True
800 while True:
801 missing = []
802 for i in items:
803 if i not in current or not current[i]:
804 missing.append(i)
805 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700806 if not missing:
807 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700808
809 for i in missing:
810 current[i] = ""
811
812 if not first:
813 print "key file %s still missing some passwords." % (self.pwfile,)
814 answer = raw_input("try to edit again? [y]> ").strip()
815 if answer and answer[0] not in 'yY':
816 raise RuntimeError("key passwords unavailable")
817 first = False
818
819 current = self.UpdateAndReadFile(current)
820
Dan Albert8b72aef2015-03-23 19:13:21 -0700821 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700822 """Prompt the user to enter a value (password) for each key in
823 'current' whose value is fales. Returns a new dict with all the
824 values.
825 """
826 result = {}
827 for k, v in sorted(current.iteritems()):
828 if v:
829 result[k] = v
830 else:
831 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700832 result[k] = getpass.getpass(
833 "Enter password for %s key> " % k).strip()
834 if result[k]:
835 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700836 return result
837
838 def UpdateAndReadFile(self, current):
839 if not self.editor or not self.pwfile:
840 return self.PromptResult(current)
841
842 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700843 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700844 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
845 f.write("# (Additional spaces are harmless.)\n\n")
846
847 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700848 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
849 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700850 f.write("[[[ %s ]]] %s\n" % (v, k))
851 if not v and first_line is None:
852 # position cursor on first line with no password.
853 first_line = i + 4
854 f.close()
855
856 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
857 _, _ = p.communicate()
858
859 return self.ReadFile()
860
861 def ReadFile(self):
862 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700863 if self.pwfile is None:
864 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700865 try:
866 f = open(self.pwfile, "r")
867 for line in f:
868 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700869 if not line or line[0] == '#':
870 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700871 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
872 if not m:
873 print "failed to parse password file: ", line
874 else:
875 result[m.group(2)] = m.group(1)
876 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700877 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700878 if e.errno != errno.ENOENT:
879 print "error reading password file: ", str(e)
880 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700881
882
Dan Albert8e0178d2015-01-27 15:53:15 -0800883def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
884 compress_type=None):
885 import datetime
886
887 # http://b/18015246
888 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
889 # for files larger than 2GiB. We can work around this by adjusting their
890 # limit. Note that `zipfile.writestr()` will not work for strings larger than
891 # 2GiB. The Python interpreter sometimes rejects strings that large (though
892 # it isn't clear to me exactly what circumstances cause this).
893 # `zipfile.write()` must be used directly to work around this.
894 #
895 # This mess can be avoided if we port to python3.
896 saved_zip64_limit = zipfile.ZIP64_LIMIT
897 zipfile.ZIP64_LIMIT = (1 << 32) - 1
898
899 if compress_type is None:
900 compress_type = zip_file.compression
901 if arcname is None:
902 arcname = filename
903
904 saved_stat = os.stat(filename)
905
906 try:
907 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
908 # file to be zipped and reset it when we're done.
909 os.chmod(filename, perms)
910
911 # Use a fixed timestamp so the output is repeatable.
912 epoch = datetime.datetime.fromtimestamp(0)
913 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
914 os.utime(filename, (timestamp, timestamp))
915
916 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
917 finally:
918 os.chmod(filename, saved_stat.st_mode)
919 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
920 zipfile.ZIP64_LIMIT = saved_zip64_limit
921
922
Tao Bao58c1b962015-05-20 09:32:18 -0700923def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700924 compress_type=None):
925 """Wrap zipfile.writestr() function to work around the zip64 limit.
926
927 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
928 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
929 when calling crc32(bytes).
930
931 But it still works fine to write a shorter string into a large zip file.
932 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
933 when we know the string won't be too long.
934 """
935
936 saved_zip64_limit = zipfile.ZIP64_LIMIT
937 zipfile.ZIP64_LIMIT = (1 << 32) - 1
938
939 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
940 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700941 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700942 if perms is None:
943 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800944 else:
Tao Baof3282b42015-04-01 11:21:55 -0700945 zinfo = zinfo_or_arcname
946
947 # If compress_type is given, it overrides the value in zinfo.
948 if compress_type is not None:
949 zinfo.compress_type = compress_type
950
Tao Bao58c1b962015-05-20 09:32:18 -0700951 # If perms is given, it has a priority.
952 if perms is not None:
953 zinfo.external_attr = perms << 16
954
Tao Baof3282b42015-04-01 11:21:55 -0700955 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700956 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
957
Dan Albert8b72aef2015-03-23 19:13:21 -0700958 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700959 zipfile.ZIP64_LIMIT = saved_zip64_limit
960
961
962def ZipClose(zip_file):
963 # http://b/18015246
964 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
965 # central directory.
966 saved_zip64_limit = zipfile.ZIP64_LIMIT
967 zipfile.ZIP64_LIMIT = (1 << 32) - 1
968
969 zip_file.close()
970
971 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700972
973
974class DeviceSpecificParams(object):
975 module = None
976 def __init__(self, **kwargs):
977 """Keyword arguments to the constructor become attributes of this
978 object, which is passed to all functions in the device-specific
979 module."""
980 for k, v in kwargs.iteritems():
981 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800982 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700983
984 if self.module is None:
985 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700986 if not path:
987 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700988 try:
989 if os.path.isdir(path):
990 info = imp.find_module("releasetools", [path])
991 else:
992 d, f = os.path.split(path)
993 b, x = os.path.splitext(f)
994 if x == ".py":
995 f = b
996 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800997 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700998 self.module = imp.load_module("device_specific", *info)
999 except ImportError:
1000 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001001
1002 def _DoCall(self, function_name, *args, **kwargs):
1003 """Call the named function in the device-specific module, passing
1004 the given args and kwargs. The first argument to the call will be
1005 the DeviceSpecific object itself. If there is no module, or the
1006 module does not define the function, return the value of the
1007 'default' kwarg (which itself defaults to None)."""
1008 if self.module is None or not hasattr(self.module, function_name):
1009 return kwargs.get("default", None)
1010 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1011
1012 def FullOTA_Assertions(self):
1013 """Called after emitting the block of assertions at the top of a
1014 full OTA package. Implementations can add whatever additional
1015 assertions they like."""
1016 return self._DoCall("FullOTA_Assertions")
1017
Doug Zongkere5ff5902012-01-17 10:55:37 -08001018 def FullOTA_InstallBegin(self):
1019 """Called at the start of full OTA installation."""
1020 return self._DoCall("FullOTA_InstallBegin")
1021
Doug Zongker05d3dea2009-06-22 11:32:31 -07001022 def FullOTA_InstallEnd(self):
1023 """Called at the end of full OTA installation; typically this is
1024 used to install the image for the device's baseband processor."""
1025 return self._DoCall("FullOTA_InstallEnd")
1026
1027 def IncrementalOTA_Assertions(self):
1028 """Called after emitting the block of assertions at the top of an
1029 incremental OTA package. Implementations can add whatever
1030 additional assertions they like."""
1031 return self._DoCall("IncrementalOTA_Assertions")
1032
Doug Zongkere5ff5902012-01-17 10:55:37 -08001033 def IncrementalOTA_VerifyBegin(self):
1034 """Called at the start of the verification phase of incremental
1035 OTA installation; additional checks can be placed here to abort
1036 the script before any changes are made."""
1037 return self._DoCall("IncrementalOTA_VerifyBegin")
1038
Doug Zongker05d3dea2009-06-22 11:32:31 -07001039 def IncrementalOTA_VerifyEnd(self):
1040 """Called at the end of the verification phase of incremental OTA
1041 installation; additional checks can be placed here to abort the
1042 script before any changes are made."""
1043 return self._DoCall("IncrementalOTA_VerifyEnd")
1044
Doug Zongkere5ff5902012-01-17 10:55:37 -08001045 def IncrementalOTA_InstallBegin(self):
1046 """Called at the start of incremental OTA installation (after
1047 verification is complete)."""
1048 return self._DoCall("IncrementalOTA_InstallBegin")
1049
Doug Zongker05d3dea2009-06-22 11:32:31 -07001050 def IncrementalOTA_InstallEnd(self):
1051 """Called at the end of incremental OTA installation; typically
1052 this is used to install the image for the device's baseband
1053 processor."""
1054 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001055
1056class File(object):
1057 def __init__(self, name, data):
1058 self.name = name
1059 self.data = data
1060 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001061 self.sha1 = sha1(data).hexdigest()
1062
1063 @classmethod
1064 def FromLocalFile(cls, name, diskname):
1065 f = open(diskname, "rb")
1066 data = f.read()
1067 f.close()
1068 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001069
1070 def WriteToTemp(self):
1071 t = tempfile.NamedTemporaryFile()
1072 t.write(self.data)
1073 t.flush()
1074 return t
1075
Geremy Condra36bd3652014-02-06 19:45:10 -08001076 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001077 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001078
1079DIFF_PROGRAM_BY_EXT = {
1080 ".gz" : "imgdiff",
1081 ".zip" : ["imgdiff", "-z"],
1082 ".jar" : ["imgdiff", "-z"],
1083 ".apk" : ["imgdiff", "-z"],
1084 ".img" : "imgdiff",
1085 }
1086
1087class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001088 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001089 self.tf = tf
1090 self.sf = sf
1091 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001092 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001093
1094 def ComputePatch(self):
1095 """Compute the patch (as a string of data) needed to turn sf into
1096 tf. Returns the same tuple as GetPatch()."""
1097
1098 tf = self.tf
1099 sf = self.sf
1100
Doug Zongker24cd2802012-08-14 16:36:15 -07001101 if self.diff_program:
1102 diff_program = self.diff_program
1103 else:
1104 ext = os.path.splitext(tf.name)[1]
1105 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001106
1107 ttemp = tf.WriteToTemp()
1108 stemp = sf.WriteToTemp()
1109
1110 ext = os.path.splitext(tf.name)[1]
1111
1112 try:
1113 ptemp = tempfile.NamedTemporaryFile()
1114 if isinstance(diff_program, list):
1115 cmd = copy.copy(diff_program)
1116 else:
1117 cmd = [diff_program]
1118 cmd.append(stemp.name)
1119 cmd.append(ttemp.name)
1120 cmd.append(ptemp.name)
1121 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001122 err = []
1123 def run():
1124 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001125 if e:
1126 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001127 th = threading.Thread(target=run)
1128 th.start()
1129 th.join(timeout=300) # 5 mins
1130 if th.is_alive():
1131 print "WARNING: diff command timed out"
1132 p.terminate()
1133 th.join(5)
1134 if th.is_alive():
1135 p.kill()
1136 th.join()
1137
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001138 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001139 print "WARNING: failure running %s:\n%s\n" % (
1140 diff_program, "".join(err))
1141 self.patch = None
1142 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001143 diff = ptemp.read()
1144 finally:
1145 ptemp.close()
1146 stemp.close()
1147 ttemp.close()
1148
1149 self.patch = diff
1150 return self.tf, self.sf, self.patch
1151
1152
1153 def GetPatch(self):
1154 """Return a tuple (target_file, source_file, patch_data).
1155 patch_data may be None if ComputePatch hasn't been called, or if
1156 computing the patch failed."""
1157 return self.tf, self.sf, self.patch
1158
1159
1160def ComputeDifferences(diffs):
1161 """Call ComputePatch on all the Difference objects in 'diffs'."""
1162 print len(diffs), "diffs to compute"
1163
1164 # Do the largest files first, to try and reduce the long-pole effect.
1165 by_size = [(i.tf.size, i) for i in diffs]
1166 by_size.sort(reverse=True)
1167 by_size = [i[1] for i in by_size]
1168
1169 lock = threading.Lock()
1170 diff_iter = iter(by_size) # accessed under lock
1171
1172 def worker():
1173 try:
1174 lock.acquire()
1175 for d in diff_iter:
1176 lock.release()
1177 start = time.time()
1178 d.ComputePatch()
1179 dur = time.time() - start
1180 lock.acquire()
1181
1182 tf, sf, patch = d.GetPatch()
1183 if sf.name == tf.name:
1184 name = tf.name
1185 else:
1186 name = "%s (%s)" % (tf.name, sf.name)
1187 if patch is None:
1188 print "patching failed! %s" % (name,)
1189 else:
1190 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1191 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1192 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001193 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001194 print e
1195 raise
1196
1197 # start worker threads; wait for them all to finish.
1198 threads = [threading.Thread(target=worker)
1199 for i in range(OPTIONS.worker_threads)]
1200 for th in threads:
1201 th.start()
1202 while threads:
1203 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001204
1205
Dan Albert8b72aef2015-03-23 19:13:21 -07001206class BlockDifference(object):
1207 def __init__(self, partition, tgt, src=None, check_first_block=False,
1208 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001209 self.tgt = tgt
1210 self.src = src
1211 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001212 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001213
Tao Bao5ece99d2015-05-12 11:42:31 -07001214 # Due to http://b/20939131, check_first_block is disabled temporarily.
1215 assert not self.check_first_block
1216
Tao Baodd2a5892015-03-12 12:32:37 -07001217 if version is None:
1218 version = 1
1219 if OPTIONS.info_dict:
1220 version = max(
1221 int(i) for i in
1222 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1223 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001224
1225 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001226 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001227 tmpdir = tempfile.mkdtemp()
1228 OPTIONS.tempfiles.append(tmpdir)
1229 self.path = os.path.join(tmpdir, partition)
1230 b.Compute(self.path)
1231
Tao Baoe09359a2015-10-13 16:37:12 -07001232 if src is None:
1233 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1234 else:
1235 _, self.device = GetTypeAndDevice("/" + partition,
1236 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001237
1238 def WriteScript(self, script, output_zip, progress=None):
1239 if not self.src:
1240 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001241 script.Print("Patching %s image unconditionally..." % (self.partition,))
1242 else:
1243 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001244
Dan Albert8b72aef2015-03-23 19:13:21 -07001245 if progress:
1246 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001247 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001248 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001249
1250 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001251 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001252 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001253 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001254 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001255 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1256 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001257 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001258 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1259 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001260 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001261 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001262 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001263 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001264 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001265 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001266 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001267 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001269
Tao Baodd2a5892015-03-12 12:32:37 -07001270 # When generating incrementals for the system and vendor partitions,
1271 # explicitly check the first block (which contains the superblock) of
1272 # the partition to see if it's what we expect. If this check fails,
1273 # give an explicit log message about the partition having been
1274 # remounted R/W (the most likely explanation) and the need to flash to
1275 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001276 if self.check_first_block:
1277 self._CheckFirstBlock(script)
1278
Tao Baodd2a5892015-03-12 12:32:37 -07001279 # Abort the OTA update. Note that the incremental OTA cannot be applied
1280 # even if it may match the checksum of the target partition.
1281 # a) If version < 3, operations like move and erase will make changes
1282 # unconditionally and damage the partition.
1283 # b) If version >= 3, it won't even reach here.
1284 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1285 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001286
Tao Bao5fcaaef2015-06-01 13:40:49 -07001287 def _WritePostInstallVerifyScript(self, script):
1288 partition = self.partition
1289 script.Print('Verifying the updated %s image...' % (partition,))
1290 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1291 ranges = self.tgt.care_map
1292 ranges_str = ranges.to_string_raw()
1293 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1294 self.device, ranges_str,
1295 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001296
1297 # Bug: 20881595
1298 # Verify that extended blocks are really zeroed out.
1299 if self.tgt.extended:
1300 ranges_str = self.tgt.extended.to_string_raw()
1301 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1302 self.device, ranges_str,
1303 self._HashZeroBlocks(self.tgt.extended.size())))
1304 script.Print('Verified the updated %s image.' % (partition,))
1305 script.AppendExtra(
1306 'else\n'
1307 ' abort("%s partition has unexpected non-zero contents after OTA '
1308 'update");\n'
1309 'endif;' % (partition,))
1310 else:
1311 script.Print('Verified the updated %s image.' % (partition,))
1312
Tao Bao5fcaaef2015-06-01 13:40:49 -07001313 script.AppendExtra(
1314 'else\n'
1315 ' abort("%s partition has unexpected contents after OTA update");\n'
1316 'endif;' % (partition,))
1317
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001318 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001319 ZipWrite(output_zip,
1320 '{}.transfer.list'.format(self.path),
1321 '{}.transfer.list'.format(self.partition))
1322 ZipWrite(output_zip,
1323 '{}.new.dat'.format(self.path),
1324 '{}.new.dat'.format(self.partition))
1325 ZipWrite(output_zip,
1326 '{}.patch.dat'.format(self.path),
1327 '{}.patch.dat'.format(self.partition),
1328 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001329
Dan Albert8e0178d2015-01-27 15:53:15 -08001330 call = ('block_image_update("{device}", '
1331 'package_extract_file("{partition}.transfer.list"), '
1332 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1333 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001334 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001335
Dan Albert8b72aef2015-03-23 19:13:21 -07001336 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001337 data = source.ReadRangeSet(ranges)
1338 ctx = sha1()
1339
1340 for p in data:
1341 ctx.update(p)
1342
1343 return ctx.hexdigest()
1344
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001345 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1346 """Return the hash value for all zero blocks."""
1347 zero_block = '\x00' * 4096
1348 ctx = sha1()
1349 for _ in range(num_blocks):
1350 ctx.update(zero_block)
1351
1352 return ctx.hexdigest()
1353
Tao Bao5ece99d2015-05-12 11:42:31 -07001354 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1355 # remounting R/W. Will change the checking to a finer-grained way to
1356 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001357 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001358 r = rangelib.RangeSet((0, 1))
1359 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001360
1361 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1362 'abort("%s has been remounted R/W; '
1363 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001364 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001365 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001366
1367DataImage = blockimgdiff.DataImage
1368
1369
Doug Zongker96a57e72010-09-26 14:57:41 -07001370# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001371PARTITION_TYPES = {
1372 "yaffs2": "MTD",
1373 "mtd": "MTD",
1374 "ext4": "EMMC",
1375 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001376 "f2fs": "EMMC",
1377 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001378}
Doug Zongker96a57e72010-09-26 14:57:41 -07001379
1380def GetTypeAndDevice(mount_point, info):
1381 fstab = info["fstab"]
1382 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001383 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1384 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001385 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001386 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001387
1388
1389def ParseCertificate(data):
1390 """Parse a PEM-format certificate."""
1391 cert = []
1392 save = False
1393 for line in data.split("\n"):
1394 if "--END CERTIFICATE--" in line:
1395 break
1396 if save:
1397 cert.append(line)
1398 if "--BEGIN CERTIFICATE--" in line:
1399 save = True
1400 cert = "".join(cert).decode('base64')
1401 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001402
Doug Zongker412c02f2014-02-13 10:58:24 -08001403def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1404 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001405 """Generate a binary patch that creates the recovery image starting
1406 with the boot image. (Most of the space in these images is just the
1407 kernel, which is identical for the two, so the resulting patch
1408 should be efficient.) Add it to the output zip, along with a shell
1409 script that is run from init.rc on first boot to actually do the
1410 patching and install the new recovery image.
1411
1412 recovery_img and boot_img should be File objects for the
1413 corresponding images. info should be the dictionary returned by
1414 common.LoadInfoDict() on the input target_files.
1415 """
1416
Doug Zongker412c02f2014-02-13 10:58:24 -08001417 if info_dict is None:
1418 info_dict = OPTIONS.info_dict
1419
Tao Bao6ed14912015-07-22 12:33:18 -07001420 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Baob11d2c52015-07-21 18:01:20 -07001421 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001422
Tao Bao6ed14912015-07-22 12:33:18 -07001423 if full_recovery_image:
1424 output_sink("etc/recovery.img", recovery_img.data)
1425
1426 else:
1427 diff_program = ["imgdiff"]
1428 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1429 if os.path.exists(path):
1430 diff_program.append("-b")
1431 diff_program.append(path)
1432 bonus_args = "-b /system/etc/recovery-resource.dat"
1433 else:
1434 bonus_args = ""
1435
1436 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1437 _, _, patch = d.ComputePatch()
1438 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001439
Dan Albertebb19aa2015-03-27 19:11:53 -07001440 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001441 # The following GetTypeAndDevice()s need to use the path in the target
1442 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001443 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1444 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1445 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001446 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001447
Tao Bao6ed14912015-07-22 12:33:18 -07001448 if full_recovery_image:
1449 sh = """#!/system/bin/sh
1450if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1451 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1452else
1453 log -t recovery "Recovery image already installed"
1454fi
1455""" % {'type': recovery_type,
1456 'device': recovery_device,
1457 'sha1': recovery_img.sha1,
1458 'size': recovery_img.size}
1459 else:
1460 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001461if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1462 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"
1463else
1464 log -t recovery "Recovery image already installed"
1465fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001466""" % {'boot_size': boot_img.size,
1467 'boot_sha1': boot_img.sha1,
1468 'recovery_size': recovery_img.size,
1469 'recovery_sha1': recovery_img.sha1,
1470 'boot_type': boot_type,
1471 'boot_device': boot_device,
1472 'recovery_type': recovery_type,
1473 'recovery_device': recovery_device,
1474 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001475
1476 # The install script location moved from /system/etc to /system/bin
Tao Bao610754e2015-07-07 18:31:47 -07001477 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001478 # target-files expects it to be, and put it there.
1479 sh_location = "etc/install-recovery.sh"
Tao Bao610754e2015-07-07 18:31:47 -07001480 found = False
Tao Baob11d2c52015-07-21 18:01:20 -07001481 if system_root_image:
1482 init_rc_dir = os.path.join(input_dir, "ROOT")
1483 else:
1484 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao610754e2015-07-07 18:31:47 -07001485 init_rc_files = os.listdir(init_rc_dir)
1486 for init_rc_file in init_rc_files:
1487 if (not init_rc_file.startswith('init.') or
1488 not init_rc_file.endswith('.rc')):
1489 continue
Tao Bao610754e2015-07-07 18:31:47 -07001490 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001491 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001492 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001493 if m:
1494 sh_location = m.group(1)
Tao Bao610754e2015-07-07 18:31:47 -07001495 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001496 break
Tao Bao610754e2015-07-07 18:31:47 -07001497 if found:
1498 break
Tao Bao610754e2015-07-07 18:31:47 -07001499 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001500
1501 output_sink(sh_location, sh)