blob: a67f044ad28164dcdb6058a018896427f77dba91 [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 Bao2ed665a2015-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"
Baligh Uddin852a5b52014-11-20 09:52:05 -080052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.verbose = False
55 self.tempfiles = []
56 self.device_specific = None
57 self.extras = {}
58 self.info_dict = None
59 self.worker_threads = None
60
61
62OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070063
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080064
65# Values for "certificate" in apkcerts that mean special things.
66SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
67
68
Dan Albert8b72aef2015-03-23 19:13:21 -070069class ExternalError(RuntimeError):
70 pass
Doug Zongkereef39442009-04-02 12:14:19 -070071
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
Tao Bao2c15d9e2015-07-09 11:51:16 -070097def LoadInfoDict(input_file, input_dir=None):
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):
Dan Albert8b72aef2015-03-23 19:13:21 -0700102 if isinstance(input_file, zipfile.ZipFile):
103 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800106 try:
107 with open(path) as f:
108 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700109 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 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:
Dan Albert8b72aef2015-03-23 19:13:21 -0700125 d["mkyaffs2_extra_flags"] = read_helper(
126 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700127 except KeyError:
128 # ok if flags don't exist
129 pass
130
131 if "recovery_api_version" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["recovery_api_version"] = read_helper(
134 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 raise ValueError("can't find recovery API version in input target-files")
137
138 if "tool_extensions" not in d:
139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 # ok if extensions don't exist
143 pass
144
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800145 if "fstab_version" not in d:
146 d["fstab_version"] = "1"
147
Tao Bao84e75682015-07-19 02:38:53 -0700148 # A few properties are stored as links to the files in the out/ directory.
149 # It works fine with the build system. However, they are no longer available
150 # when (re)generating from target_files zip. If input_dir is not None, we
151 # are doing repacking. Redirect those properties to the actual files in the
152 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700153 if input_dir is not None:
Tao Bao84e75682015-07-19 02:38:53 -0700154 # We carry a copy of file_contexts under META/. If not available, search
155 # BOOT/RAMDISK/. Note that sometimes we may need a different file_contexts
156 # to build images than the one running on device, such as when enabling
157 # system_root_image. In that case, we must have the one for image
158 # generation copied to META/.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700159 fc_config = os.path.join(input_dir, "META", "file_contexts")
Tao Bao84e75682015-07-19 02:38:53 -0700160 if d.get("system_root_image") == "true":
161 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700162 if not os.path.exists(fc_config):
163 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
164 if not os.path.exists(fc_config):
165 fc_config = None
166
167 if fc_config:
168 d["selinux_fc"] = fc_config
169
Tao Bao84e75682015-07-19 02:38:53 -0700170 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
171 if d.get("system_root_image") == "true":
172 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
173 d["ramdisk_fs_config"] = os.path.join(
174 input_dir, "META", "root_filesystem_config.txt")
175
Doug Zongker37974732010-09-16 17:44:38 -0700176 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800177 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700178 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700179 if not line:
180 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700181 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700182 if not value:
183 continue
Doug Zongker37974732010-09-16 17:44:38 -0700184 if name == "blocksize":
185 d[name] = value
186 else:
187 d[name + "_size"] = value
188 except KeyError:
189 pass
190
191 def makeint(key):
192 if key in d:
193 d[key] = int(d[key], 0)
194
195 makeint("recovery_api_version")
196 makeint("blocksize")
197 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700198 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700199 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700200 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700201 makeint("recovery_size")
202 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800203 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700204
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700205 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
206 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800207 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700208 return d
209
Doug Zongkerc9253822014-02-04 12:17:58 -0800210def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700211 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800212 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700213 except KeyError:
214 print "Warning: could not find SYSTEM/build.prop in %s" % zip
215 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700216 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700217
Michael Runge6e836112014-04-15 17:40:21 -0700218def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700219 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700220 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700221 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700222 if not line or line.startswith("#"):
223 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700224 if "=" in line:
225 name, value = line.split("=", 1)
226 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700227 return d
228
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700229def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700230 class Partition(object):
Tao Baodf06e962015-06-10 12:32:41 -0700231 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700232 self.mount_point = mount_point
233 self.fs_type = fs_type
234 self.device = device
235 self.length = length
236 self.device2 = device2
Tao Baodf06e962015-06-10 12:32:41 -0700237 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700238
239 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800240 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700241 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800242 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700243 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700244
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800245 if fstab_version == 1:
246 d = {}
247 for line in data.split("\n"):
248 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700249 if not line or line.startswith("#"):
250 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800251 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800254 options = None
255 if len(pieces) >= 4:
256 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700257 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 if len(pieces) >= 5:
259 options = pieces[4]
260 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700261 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800262 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800263 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700265
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 mount_point = pieces[0]
267 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800268 if options:
269 options = options.split(",")
270 for i in options:
271 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800273 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700274 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
277 device=pieces[2], length=length,
278 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279
280 elif fstab_version == 2:
281 d = {}
282 for line in data.split("\n"):
283 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700284 if not line or line.startswith("#"):
285 continue
Tao Baodf06e962015-06-10 12:32:41 -0700286 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800287 pieces = line.split()
288 if len(pieces) != 5:
289 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
290
291 # Ignore entries that are managed by vold
292 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700293 if "voldmanaged=" in options:
294 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800295
296 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700297 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800298 options = options.split(",")
299 for i in options:
300 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700301 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800302 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800303 # Ignore all unknown options in the unified fstab
304 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800305
Tao Baodf06e962015-06-10 12:32:41 -0700306 mount_flags = pieces[3]
307 # Honor the SELinux context if present.
308 context = None
309 for i in mount_flags.split(","):
310 if i.startswith("context="):
311 context = i
312
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 mount_point = pieces[1]
314 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Baodf06e962015-06-10 12:32:41 -0700315 device=pieces[0], length=length,
316 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800317
318 else:
319 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
320
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700321 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700322 # system. Other areas assume system is always at "/system" so point /system
323 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700324 if system_root_image:
325 assert not d.has_key("/system") and d.has_key("/")
326 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700327 return d
328
329
Doug Zongker37974732010-09-16 17:44:38 -0700330def DumpInfoDict(d):
331 for k, v in sorted(d.items()):
332 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700333
Dan Albert8b72aef2015-03-23 19:13:21 -0700334
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700335def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
336 has_ramdisk=False):
337 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700338
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700339 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
340 'sourcedir'), and turn them into a boot image. Return the image data, or
341 None if sourcedir does not appear to contains files for building the
342 requested image."""
343
344 def make_ramdisk():
345 ramdisk_img = tempfile.NamedTemporaryFile()
346
347 if os.access(fs_config_file, os.F_OK):
348 cmd = ["mkbootfs", "-f", fs_config_file,
349 os.path.join(sourcedir, "RAMDISK")]
350 else:
351 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
352 p1 = Run(cmd, stdout=subprocess.PIPE)
353 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
354
355 p2.wait()
356 p1.wait()
357 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
358 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
359
360 return ramdisk_img
361
362 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
363 return None
364
365 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700366 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700367
Doug Zongkerd5131602012-08-02 14:46:42 -0700368 if info_dict is None:
369 info_dict = OPTIONS.info_dict
370
Doug Zongkereef39442009-04-02 12:14:19 -0700371 img = tempfile.NamedTemporaryFile()
372
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700373 if has_ramdisk:
374 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700375
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800376 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
377 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
378
379 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700380
Benoit Fradina45a8682014-07-14 21:00:43 +0200381 fn = os.path.join(sourcedir, "second")
382 if os.access(fn, os.F_OK):
383 cmd.append("--second")
384 cmd.append(fn)
385
Doug Zongker171f1cd2009-06-15 22:36:37 -0700386 fn = os.path.join(sourcedir, "cmdline")
387 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700388 cmd.append("--cmdline")
389 cmd.append(open(fn).read().rstrip("\n"))
390
391 fn = os.path.join(sourcedir, "base")
392 if os.access(fn, os.F_OK):
393 cmd.append("--base")
394 cmd.append(open(fn).read().rstrip("\n"))
395
Ying Wang4de6b5b2010-08-25 14:29:34 -0700396 fn = os.path.join(sourcedir, "pagesize")
397 if os.access(fn, os.F_OK):
398 cmd.append("--pagesize")
399 cmd.append(open(fn).read().rstrip("\n"))
400
Doug Zongkerd5131602012-08-02 14:46:42 -0700401 args = info_dict.get("mkbootimg_args", None)
402 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700403 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700404
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700405 if has_ramdisk:
406 cmd.extend(["--ramdisk", ramdisk_img.name])
407
Tao Baod95e9fd2015-03-29 23:07:41 -0700408 img_unsigned = None
409 if info_dict.get("vboot", None):
410 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700411 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700412 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700413 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700414
415 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700416 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700417 assert p.returncode == 0, "mkbootimg of %s image failed" % (
418 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700419
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700420 if info_dict.get("verity_key", None):
421 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800422 cmd = [OPTIONS.boot_signer_path, path, img.name,
423 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700424 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700425 p = Run(cmd, stdout=subprocess.PIPE)
426 p.communicate()
427 assert p.returncode == 0, "boot_signer of %s image failed" % path
428
Tao Baod95e9fd2015-03-29 23:07:41 -0700429 # Sign the image if vboot is non-empty.
430 elif info_dict.get("vboot", None):
431 path = "/" + os.path.basename(sourcedir).lower()
432 img_keyblock = tempfile.NamedTemporaryFile()
433 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
434 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
435 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
436 img.name]
437 p = Run(cmd, stdout=subprocess.PIPE)
438 p.communicate()
439 assert p.returncode == 0, "vboot_signer of %s image failed" % path
440
Tao Bao2ed665a2015-04-01 11:21:55 -0700441 # Clean up the temp files.
442 img_unsigned.close()
443 img_keyblock.close()
444
Doug Zongkereef39442009-04-02 12:14:19 -0700445 img.seek(os.SEEK_SET, 0)
446 data = img.read()
447
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700448 if has_ramdisk:
449 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700450 img.close()
451
452 return data
453
454
Doug Zongkerd5131602012-08-02 14:46:42 -0700455def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
456 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700457 """Return a File object with the desired bootable image.
458
459 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
460 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
461 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700462
Doug Zongker55d93282011-01-25 17:03:34 -0800463 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
464 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700465 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800466 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700467
468 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
469 if os.path.exists(prebuilt_path):
470 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
471 return File.FromLocalFile(name, prebuilt_path)
472
473 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700474
475 if info_dict is None:
476 info_dict = OPTIONS.info_dict
477
478 # With system_root_image == "true", we don't pack ramdisk into the boot image.
479 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
480 prebuilt_name != "boot.img")
481
Doug Zongker6f1d0312014-08-22 08:07:12 -0700482 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700483 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
484 os.path.join(unpack_dir, fs_config),
485 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700486 if data:
487 return File(name, data)
488 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800489
Doug Zongkereef39442009-04-02 12:14:19 -0700490
Doug Zongker75f17362009-12-08 13:46:44 -0800491def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800492 """Unzip the given archive into a temporary directory and return the name.
493
494 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
495 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
496
497 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
498 main file), open for reading.
499 """
Doug Zongkereef39442009-04-02 12:14:19 -0700500
501 tmp = tempfile.mkdtemp(prefix="targetfiles-")
502 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800503
504 def unzip_to_dir(filename, dirname):
505 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
506 if pattern is not None:
507 cmd.append(pattern)
508 p = Run(cmd, stdout=subprocess.PIPE)
509 p.communicate()
510 if p.returncode != 0:
511 raise ExternalError("failed to unzip input target-files \"%s\"" %
512 (filename,))
513
514 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
515 if m:
516 unzip_to_dir(m.group(1), tmp)
517 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
518 filename = m.group(1)
519 else:
520 unzip_to_dir(filename, tmp)
521
522 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700523
524
525def GetKeyPasswords(keylist):
526 """Given a list of keys, prompt the user to enter passwords for
527 those which require them. Return a {key: password} dict. password
528 will be None if the key has no password."""
529
Doug Zongker8ce7c252009-05-22 13:34:54 -0700530 no_passwords = []
531 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700532 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700533 devnull = open("/dev/null", "w+b")
534 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800535 # We don't need a password for things that aren't really keys.
536 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700537 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700538 continue
539
T.R. Fullhart37e10522013-03-18 10:31:26 -0700540 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700541 "-inform", "DER", "-nocrypt"],
542 stdin=devnull.fileno(),
543 stdout=devnull.fileno(),
544 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700545 p.communicate()
546 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700547 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700548 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700549 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700550 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
551 "-inform", "DER", "-passin", "pass:"],
552 stdin=devnull.fileno(),
553 stdout=devnull.fileno(),
554 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700555 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700556 if p.returncode == 0:
557 # Encrypted key with empty string as password.
558 key_passwords[k] = ''
559 elif stderr.startswith('Error decrypting key'):
560 # Definitely encrypted key.
561 # It would have said "Error reading key" if it didn't parse correctly.
562 need_passwords.append(k)
563 else:
564 # Potentially, a type of key that openssl doesn't understand.
565 # We'll let the routines in signapk.jar handle it.
566 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700567 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700568
T.R. Fullhart37e10522013-03-18 10:31:26 -0700569 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700570 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700571 return key_passwords
572
573
Doug Zongker951495f2009-08-14 12:44:19 -0700574def SignFile(input_name, output_name, key, password, align=None,
575 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700576 """Sign the input_name zip/jar/apk, producing output_name. Use the
577 given key and password (the latter may be None if the key does not
578 have a password.
579
580 If align is an integer > 1, zipalign is run to align stored files in
581 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700582
583 If whole_file is true, use the "-w" option to SignApk to embed a
584 signature that covers the whole file in the archive comment of the
585 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700586 """
Doug Zongker951495f2009-08-14 12:44:19 -0700587
Doug Zongkereef39442009-04-02 12:14:19 -0700588 if align == 0 or align == 1:
589 align = None
590
591 if align:
592 temp = tempfile.NamedTemporaryFile()
593 sign_name = temp.name
594 else:
595 sign_name = output_name
596
Baligh Uddin339ee492014-09-05 11:18:07 -0700597 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700598 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
599 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700600 if whole_file:
601 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700602 cmd.extend([key + OPTIONS.public_key_suffix,
603 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700604 input_name, sign_name])
605
606 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700607 if password is not None:
608 password += "\n"
609 p.communicate(password)
610 if p.returncode != 0:
611 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
612
613 if align:
Brian Carlstrom663127d2015-05-22 15:51:19 -0700614 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700615 p.communicate()
616 if p.returncode != 0:
617 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
618 temp.close()
619
620
Doug Zongker37974732010-09-16 17:44:38 -0700621def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700622 """Check the data string passed against the max size limit, if
623 any, for the given target. Raise exception if the data is too big.
624 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700625
Dan Albert8b72aef2015-03-23 19:13:21 -0700626 if target.endswith(".img"):
627 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700628 mount_point = "/" + target
629
Ying Wangf8824af2014-06-03 14:07:27 -0700630 fs_type = None
631 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700632 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700633 if mount_point == "/userdata":
634 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700635 p = info_dict["fstab"][mount_point]
636 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800637 device = p.device
638 if "/" in device:
639 device = device[device.rfind("/")+1:]
640 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700641 if not fs_type or not limit:
642 return
Doug Zongkereef39442009-04-02 12:14:19 -0700643
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700644 if fs_type == "yaffs2":
645 # image size should be increased by 1/64th to account for the
646 # spare area (64 bytes per 2k page)
647 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800648 size = len(data)
649 pct = float(size) * 100.0 / limit
650 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
651 if pct >= 99.0:
652 raise ExternalError(msg)
653 elif pct >= 95.0:
654 print
655 print " WARNING: ", msg
656 print
657 elif OPTIONS.verbose:
658 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700659
660
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800661def ReadApkCerts(tf_zip):
662 """Given a target_files ZipFile, parse the META/apkcerts.txt file
663 and return a {package: cert} dict."""
664 certmap = {}
665 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
666 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700667 if not line:
668 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800669 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
670 r'private_key="(.*)"$', line)
671 if m:
672 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700673 public_key_suffix_len = len(OPTIONS.public_key_suffix)
674 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800675 if cert in SPECIAL_CERT_STRINGS and not privkey:
676 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700677 elif (cert.endswith(OPTIONS.public_key_suffix) and
678 privkey.endswith(OPTIONS.private_key_suffix) and
679 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
680 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800681 else:
682 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
683 return certmap
684
685
Doug Zongkereef39442009-04-02 12:14:19 -0700686COMMON_DOCSTRING = """
687 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700688 Prepend <dir>/bin to the list of places to search for binaries
689 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700690
Doug Zongker05d3dea2009-06-22 11:32:31 -0700691 -s (--device_specific) <file>
692 Path to the python module containing device-specific
693 releasetools code.
694
Doug Zongker8bec09e2009-11-30 15:37:14 -0800695 -x (--extra) <key=value>
696 Add a key/value pair to the 'extras' dict, which device-specific
697 extension code may look at.
698
Doug Zongkereef39442009-04-02 12:14:19 -0700699 -v (--verbose)
700 Show command lines being executed.
701
702 -h (--help)
703 Display this usage message and exit.
704"""
705
706def Usage(docstring):
707 print docstring.rstrip("\n")
708 print COMMON_DOCSTRING
709
710
711def ParseOptions(argv,
712 docstring,
713 extra_opts="", extra_long_opts=(),
714 extra_option_handler=None):
715 """Parse the options in argv and return any arguments that aren't
716 flags. docstring is the calling module's docstring, to be displayed
717 for errors and -h. extra_opts and extra_long_opts are for flags
718 defined by the caller, which are processed by passing them to
719 extra_option_handler."""
720
721 try:
722 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800723 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700724 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700725 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800726 "private_key_suffix=", "boot_signer_path=", "device_specific=",
727 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700728 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700729 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700730 Usage(docstring)
731 print "**", str(err), "**"
732 sys.exit(2)
733
Doug Zongkereef39442009-04-02 12:14:19 -0700734 for o, a in opts:
735 if o in ("-h", "--help"):
736 Usage(docstring)
737 sys.exit()
738 elif o in ("-v", "--verbose"):
739 OPTIONS.verbose = True
740 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700741 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700742 elif o in ("--signapk_path",):
743 OPTIONS.signapk_path = a
744 elif o in ("--extra_signapk_args",):
745 OPTIONS.extra_signapk_args = shlex.split(a)
746 elif o in ("--java_path",):
747 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700748 elif o in ("--java_args",):
749 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700750 elif o in ("--public_key_suffix",):
751 OPTIONS.public_key_suffix = a
752 elif o in ("--private_key_suffix",):
753 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800754 elif o in ("--boot_signer_path",):
755 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700756 elif o in ("-s", "--device_specific"):
757 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800758 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800759 key, value = a.split("=", 1)
760 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700761 else:
762 if extra_option_handler is None or not extra_option_handler(o, a):
763 assert False, "unknown option \"%s\"" % (o,)
764
Doug Zongker85448772014-09-09 14:59:20 -0700765 if OPTIONS.search_path:
766 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
767 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700768
769 return args
770
771
Doug Zongkerfc44a512014-08-26 13:10:25 -0700772def MakeTempFile(prefix=None, suffix=None):
773 """Make a temp file and add it to the list of things to be deleted
774 when Cleanup() is called. Return the filename."""
775 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
776 os.close(fd)
777 OPTIONS.tempfiles.append(fn)
778 return fn
779
780
Doug Zongkereef39442009-04-02 12:14:19 -0700781def Cleanup():
782 for i in OPTIONS.tempfiles:
783 if os.path.isdir(i):
784 shutil.rmtree(i)
785 else:
786 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700787
788
789class PasswordManager(object):
790 def __init__(self):
791 self.editor = os.getenv("EDITOR", None)
792 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
793
794 def GetPasswords(self, items):
795 """Get passwords corresponding to each string in 'items',
796 returning a dict. (The dict may have keys in addition to the
797 values in 'items'.)
798
799 Uses the passwords in $ANDROID_PW_FILE if available, letting the
800 user edit that file to add more needed passwords. If no editor is
801 available, or $ANDROID_PW_FILE isn't define, prompts the user
802 interactively in the ordinary way.
803 """
804
805 current = self.ReadFile()
806
807 first = True
808 while True:
809 missing = []
810 for i in items:
811 if i not in current or not current[i]:
812 missing.append(i)
813 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700814 if not missing:
815 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700816
817 for i in missing:
818 current[i] = ""
819
820 if not first:
821 print "key file %s still missing some passwords." % (self.pwfile,)
822 answer = raw_input("try to edit again? [y]> ").strip()
823 if answer and answer[0] not in 'yY':
824 raise RuntimeError("key passwords unavailable")
825 first = False
826
827 current = self.UpdateAndReadFile(current)
828
Dan Albert8b72aef2015-03-23 19:13:21 -0700829 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700830 """Prompt the user to enter a value (password) for each key in
831 'current' whose value is fales. Returns a new dict with all the
832 values.
833 """
834 result = {}
835 for k, v in sorted(current.iteritems()):
836 if v:
837 result[k] = v
838 else:
839 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700840 result[k] = getpass.getpass(
841 "Enter password for %s key> " % k).strip()
842 if result[k]:
843 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700844 return result
845
846 def UpdateAndReadFile(self, current):
847 if not self.editor or not self.pwfile:
848 return self.PromptResult(current)
849
850 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700851 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700852 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
853 f.write("# (Additional spaces are harmless.)\n\n")
854
855 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700856 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
857 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700858 f.write("[[[ %s ]]] %s\n" % (v, k))
859 if not v and first_line is None:
860 # position cursor on first line with no password.
861 first_line = i + 4
862 f.close()
863
864 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
865 _, _ = p.communicate()
866
867 return self.ReadFile()
868
869 def ReadFile(self):
870 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700871 if self.pwfile is None:
872 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700873 try:
874 f = open(self.pwfile, "r")
875 for line in f:
876 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700877 if not line or line[0] == '#':
878 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700879 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
880 if not m:
881 print "failed to parse password file: ", line
882 else:
883 result[m.group(2)] = m.group(1)
884 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700885 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700886 if e.errno != errno.ENOENT:
887 print "error reading password file: ", str(e)
888 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700889
890
Dan Albert8e0178d2015-01-27 15:53:15 -0800891def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
892 compress_type=None):
893 import datetime
894
895 # http://b/18015246
896 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
897 # for files larger than 2GiB. We can work around this by adjusting their
898 # limit. Note that `zipfile.writestr()` will not work for strings larger than
899 # 2GiB. The Python interpreter sometimes rejects strings that large (though
900 # it isn't clear to me exactly what circumstances cause this).
901 # `zipfile.write()` must be used directly to work around this.
902 #
903 # This mess can be avoided if we port to python3.
904 saved_zip64_limit = zipfile.ZIP64_LIMIT
905 zipfile.ZIP64_LIMIT = (1 << 32) - 1
906
907 if compress_type is None:
908 compress_type = zip_file.compression
909 if arcname is None:
910 arcname = filename
911
912 saved_stat = os.stat(filename)
913
914 try:
915 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
916 # file to be zipped and reset it when we're done.
917 os.chmod(filename, perms)
918
919 # Use a fixed timestamp so the output is repeatable.
920 epoch = datetime.datetime.fromtimestamp(0)
921 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
922 os.utime(filename, (timestamp, timestamp))
923
924 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
925 finally:
926 os.chmod(filename, saved_stat.st_mode)
927 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
928 zipfile.ZIP64_LIMIT = saved_zip64_limit
929
930
Tao Bao97734652015-05-20 09:32:18 -0700931def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Bao2ed665a2015-04-01 11:21:55 -0700932 compress_type=None):
933 """Wrap zipfile.writestr() function to work around the zip64 limit.
934
935 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
936 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
937 when calling crc32(bytes).
938
939 But it still works fine to write a shorter string into a large zip file.
940 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
941 when we know the string won't be too long.
942 """
943
944 saved_zip64_limit = zipfile.ZIP64_LIMIT
945 zipfile.ZIP64_LIMIT = (1 << 32) - 1
946
947 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
948 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700949 zinfo.compress_type = zip_file.compression
Tao Bao97734652015-05-20 09:32:18 -0700950 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700951 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800952 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700953 zinfo = zinfo_or_arcname
954
955 # If compress_type is given, it overrides the value in zinfo.
956 if compress_type is not None:
957 zinfo.compress_type = compress_type
958
Tao Bao97734652015-05-20 09:32:18 -0700959 # If perms is given, it has a priority.
960 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700961 # If perms doesn't set the file type, mark it as a regular file.
962 if perms & 0o770000 == 0:
963 perms |= 0o100000
Tao Bao97734652015-05-20 09:32:18 -0700964 zinfo.external_attr = perms << 16
965
Tao Bao2ed665a2015-04-01 11:21:55 -0700966 # Use a fixed timestamp so the output is repeatable.
Tao Bao2ed665a2015-04-01 11:21:55 -0700967 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
968
Dan Albert8b72aef2015-03-23 19:13:21 -0700969 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700970 zipfile.ZIP64_LIMIT = saved_zip64_limit
971
972
973def ZipClose(zip_file):
974 # http://b/18015246
975 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
976 # central directory.
977 saved_zip64_limit = zipfile.ZIP64_LIMIT
978 zipfile.ZIP64_LIMIT = (1 << 32) - 1
979
980 zip_file.close()
981
982 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700983
984
985class DeviceSpecificParams(object):
986 module = None
987 def __init__(self, **kwargs):
988 """Keyword arguments to the constructor become attributes of this
989 object, which is passed to all functions in the device-specific
990 module."""
991 for k, v in kwargs.iteritems():
992 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800993 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700994
995 if self.module is None:
996 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700997 if not path:
998 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700999 try:
1000 if os.path.isdir(path):
1001 info = imp.find_module("releasetools", [path])
1002 else:
1003 d, f = os.path.split(path)
1004 b, x = os.path.splitext(f)
1005 if x == ".py":
1006 f = b
1007 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001008 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001009 self.module = imp.load_module("device_specific", *info)
1010 except ImportError:
1011 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001012
1013 def _DoCall(self, function_name, *args, **kwargs):
1014 """Call the named function in the device-specific module, passing
1015 the given args and kwargs. The first argument to the call will be
1016 the DeviceSpecific object itself. If there is no module, or the
1017 module does not define the function, return the value of the
1018 'default' kwarg (which itself defaults to None)."""
1019 if self.module is None or not hasattr(self.module, function_name):
1020 return kwargs.get("default", None)
1021 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1022
1023 def FullOTA_Assertions(self):
1024 """Called after emitting the block of assertions at the top of a
1025 full OTA package. Implementations can add whatever additional
1026 assertions they like."""
1027 return self._DoCall("FullOTA_Assertions")
1028
Doug Zongkere5ff5902012-01-17 10:55:37 -08001029 def FullOTA_InstallBegin(self):
1030 """Called at the start of full OTA installation."""
1031 return self._DoCall("FullOTA_InstallBegin")
1032
Doug Zongker05d3dea2009-06-22 11:32:31 -07001033 def FullOTA_InstallEnd(self):
1034 """Called at the end of full OTA installation; typically this is
1035 used to install the image for the device's baseband processor."""
1036 return self._DoCall("FullOTA_InstallEnd")
1037
1038 def IncrementalOTA_Assertions(self):
1039 """Called after emitting the block of assertions at the top of an
1040 incremental OTA package. Implementations can add whatever
1041 additional assertions they like."""
1042 return self._DoCall("IncrementalOTA_Assertions")
1043
Doug Zongkere5ff5902012-01-17 10:55:37 -08001044 def IncrementalOTA_VerifyBegin(self):
1045 """Called at the start of the verification phase of incremental
1046 OTA installation; additional checks can be placed here to abort
1047 the script before any changes are made."""
1048 return self._DoCall("IncrementalOTA_VerifyBegin")
1049
Doug Zongker05d3dea2009-06-22 11:32:31 -07001050 def IncrementalOTA_VerifyEnd(self):
1051 """Called at the end of the verification phase of incremental OTA
1052 installation; additional checks can be placed here to abort the
1053 script before any changes are made."""
1054 return self._DoCall("IncrementalOTA_VerifyEnd")
1055
Doug Zongkere5ff5902012-01-17 10:55:37 -08001056 def IncrementalOTA_InstallBegin(self):
1057 """Called at the start of incremental OTA installation (after
1058 verification is complete)."""
1059 return self._DoCall("IncrementalOTA_InstallBegin")
1060
Doug Zongker05d3dea2009-06-22 11:32:31 -07001061 def IncrementalOTA_InstallEnd(self):
1062 """Called at the end of incremental OTA installation; typically
1063 this is used to install the image for the device's baseband
1064 processor."""
1065 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001066
1067class File(object):
1068 def __init__(self, name, data):
1069 self.name = name
1070 self.data = data
1071 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001072 self.sha1 = sha1(data).hexdigest()
1073
1074 @classmethod
1075 def FromLocalFile(cls, name, diskname):
1076 f = open(diskname, "rb")
1077 data = f.read()
1078 f.close()
1079 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001080
1081 def WriteToTemp(self):
1082 t = tempfile.NamedTemporaryFile()
1083 t.write(self.data)
1084 t.flush()
1085 return t
1086
Geremy Condra36bd3652014-02-06 19:45:10 -08001087 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001088 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001089
1090DIFF_PROGRAM_BY_EXT = {
1091 ".gz" : "imgdiff",
1092 ".zip" : ["imgdiff", "-z"],
1093 ".jar" : ["imgdiff", "-z"],
1094 ".apk" : ["imgdiff", "-z"],
1095 ".img" : "imgdiff",
1096 }
1097
1098class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001099 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001100 self.tf = tf
1101 self.sf = sf
1102 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001103 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001104
1105 def ComputePatch(self):
1106 """Compute the patch (as a string of data) needed to turn sf into
1107 tf. Returns the same tuple as GetPatch()."""
1108
1109 tf = self.tf
1110 sf = self.sf
1111
Doug Zongker24cd2802012-08-14 16:36:15 -07001112 if self.diff_program:
1113 diff_program = self.diff_program
1114 else:
1115 ext = os.path.splitext(tf.name)[1]
1116 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001117
1118 ttemp = tf.WriteToTemp()
1119 stemp = sf.WriteToTemp()
1120
1121 ext = os.path.splitext(tf.name)[1]
1122
1123 try:
1124 ptemp = tempfile.NamedTemporaryFile()
1125 if isinstance(diff_program, list):
1126 cmd = copy.copy(diff_program)
1127 else:
1128 cmd = [diff_program]
1129 cmd.append(stemp.name)
1130 cmd.append(ttemp.name)
1131 cmd.append(ptemp.name)
1132 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001133 err = []
1134 def run():
1135 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001136 if e:
1137 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001138 th = threading.Thread(target=run)
1139 th.start()
1140 th.join(timeout=300) # 5 mins
1141 if th.is_alive():
1142 print "WARNING: diff command timed out"
1143 p.terminate()
1144 th.join(5)
1145 if th.is_alive():
1146 p.kill()
1147 th.join()
1148
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001149 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001150 print "WARNING: failure running %s:\n%s\n" % (
1151 diff_program, "".join(err))
1152 self.patch = None
1153 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001154 diff = ptemp.read()
1155 finally:
1156 ptemp.close()
1157 stemp.close()
1158 ttemp.close()
1159
1160 self.patch = diff
1161 return self.tf, self.sf, self.patch
1162
1163
1164 def GetPatch(self):
1165 """Return a tuple (target_file, source_file, patch_data).
1166 patch_data may be None if ComputePatch hasn't been called, or if
1167 computing the patch failed."""
1168 return self.tf, self.sf, self.patch
1169
1170
1171def ComputeDifferences(diffs):
1172 """Call ComputePatch on all the Difference objects in 'diffs'."""
1173 print len(diffs), "diffs to compute"
1174
1175 # Do the largest files first, to try and reduce the long-pole effect.
1176 by_size = [(i.tf.size, i) for i in diffs]
1177 by_size.sort(reverse=True)
1178 by_size = [i[1] for i in by_size]
1179
1180 lock = threading.Lock()
1181 diff_iter = iter(by_size) # accessed under lock
1182
1183 def worker():
1184 try:
1185 lock.acquire()
1186 for d in diff_iter:
1187 lock.release()
1188 start = time.time()
1189 d.ComputePatch()
1190 dur = time.time() - start
1191 lock.acquire()
1192
1193 tf, sf, patch = d.GetPatch()
1194 if sf.name == tf.name:
1195 name = tf.name
1196 else:
1197 name = "%s (%s)" % (tf.name, sf.name)
1198 if patch is None:
1199 print "patching failed! %s" % (name,)
1200 else:
1201 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1202 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1203 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001204 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001205 print e
1206 raise
1207
1208 # start worker threads; wait for them all to finish.
1209 threads = [threading.Thread(target=worker)
1210 for i in range(OPTIONS.worker_threads)]
1211 for th in threads:
1212 th.start()
1213 while threads:
1214 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001215
1216
Dan Albert8b72aef2015-03-23 19:13:21 -07001217class BlockDifference(object):
1218 def __init__(self, partition, tgt, src=None, check_first_block=False,
1219 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001220 self.tgt = tgt
1221 self.src = src
1222 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001223 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001224
Tao Baoff777812015-05-12 11:42:31 -07001225 # Due to http://b/20939131, check_first_block is disabled temporarily.
1226 assert not self.check_first_block
1227
Tao Baodd2a5892015-03-12 12:32:37 -07001228 if version is None:
1229 version = 1
1230 if OPTIONS.info_dict:
1231 version = max(
1232 int(i) for i in
1233 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1234 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001235
1236 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001237 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001238 tmpdir = tempfile.mkdtemp()
1239 OPTIONS.tempfiles.append(tmpdir)
1240 self.path = os.path.join(tmpdir, partition)
1241 b.Compute(self.path)
1242
1243 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1244
1245 def WriteScript(self, script, output_zip, progress=None):
1246 if not self.src:
1247 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001248 script.Print("Patching %s image unconditionally..." % (self.partition,))
1249 else:
1250 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001251
Dan Albert8b72aef2015-03-23 19:13:21 -07001252 if progress:
1253 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001254 self._WriteUpdate(script, output_zip)
Tao Bao68658c02015-06-01 13:40:49 -07001255 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001256
1257 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001258 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001259 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001260 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001261 else:
Tao Baoff777812015-05-12 11:42:31 -07001262 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1263 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001264 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001265 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1266 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001267 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001268 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baoff777812015-05-12 11:42:31 -07001269 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001270 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001271 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001272 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Baoff777812015-05-12 11:42:31 -07001273 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001274 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001275 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001276
Tao Baodd2a5892015-03-12 12:32:37 -07001277 # When generating incrementals for the system and vendor partitions,
1278 # explicitly check the first block (which contains the superblock) of
1279 # the partition to see if it's what we expect. If this check fails,
1280 # give an explicit log message about the partition having been
1281 # remounted R/W (the most likely explanation) and the need to flash to
1282 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001283 if self.check_first_block:
1284 self._CheckFirstBlock(script)
1285
Tao Baodd2a5892015-03-12 12:32:37 -07001286 # Abort the OTA update. Note that the incremental OTA cannot be applied
1287 # even if it may match the checksum of the target partition.
1288 # a) If version < 3, operations like move and erase will make changes
1289 # unconditionally and damage the partition.
1290 # b) If version >= 3, it won't even reach here.
1291 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1292 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001293
Tao Bao68658c02015-06-01 13:40:49 -07001294 def _WritePostInstallVerifyScript(self, script):
1295 partition = self.partition
1296 script.Print('Verifying the updated %s image...' % (partition,))
1297 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1298 ranges = self.tgt.care_map
1299 ranges_str = ranges.to_string_raw()
1300 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1301 self.device, ranges_str,
1302 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001303
1304 # Bug: 20881595
1305 # Verify that extended blocks are really zeroed out.
1306 if self.tgt.extended:
1307 ranges_str = self.tgt.extended.to_string_raw()
1308 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1309 self.device, ranges_str,
1310 self._HashZeroBlocks(self.tgt.extended.size())))
1311 script.Print('Verified the updated %s image.' % (partition,))
1312 script.AppendExtra(
1313 'else\n'
1314 ' abort("%s partition has unexpected non-zero contents after OTA '
1315 'update");\n'
1316 'endif;' % (partition,))
1317 else:
1318 script.Print('Verified the updated %s image.' % (partition,))
1319
Tao Bao68658c02015-06-01 13:40:49 -07001320 script.AppendExtra(
1321 'else\n'
1322 ' abort("%s partition has unexpected contents after OTA update");\n'
1323 'endif;' % (partition,))
1324
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001325 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001326 ZipWrite(output_zip,
1327 '{}.transfer.list'.format(self.path),
1328 '{}.transfer.list'.format(self.partition))
1329 ZipWrite(output_zip,
1330 '{}.new.dat'.format(self.path),
1331 '{}.new.dat'.format(self.partition))
1332 ZipWrite(output_zip,
1333 '{}.patch.dat'.format(self.path),
1334 '{}.patch.dat'.format(self.partition),
1335 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001336
Dan Albert8e0178d2015-01-27 15:53:15 -08001337 call = ('block_image_update("{device}", '
1338 'package_extract_file("{partition}.transfer.list"), '
1339 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1340 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001341 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001342
Dan Albert8b72aef2015-03-23 19:13:21 -07001343 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001344 data = source.ReadRangeSet(ranges)
1345 ctx = sha1()
1346
1347 for p in data:
1348 ctx.update(p)
1349
1350 return ctx.hexdigest()
1351
Tao Baoe9b61912015-07-09 17:37:49 -07001352 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1353 """Return the hash value for all zero blocks."""
1354 zero_block = '\x00' * 4096
1355 ctx = sha1()
1356 for _ in range(num_blocks):
1357 ctx.update(zero_block)
1358
1359 return ctx.hexdigest()
1360
Tao Baoff777812015-05-12 11:42:31 -07001361 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1362 # remounting R/W. Will change the checking to a finer-grained way to
1363 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001364 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001365 r = rangelib.RangeSet((0, 1))
1366 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001367
1368 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1369 'abort("%s has been remounted R/W; '
1370 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001371 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001372 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001373
1374DataImage = blockimgdiff.DataImage
1375
1376
Doug Zongker96a57e72010-09-26 14:57:41 -07001377# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001378PARTITION_TYPES = {
1379 "yaffs2": "MTD",
1380 "mtd": "MTD",
1381 "ext4": "EMMC",
1382 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001383 "f2fs": "EMMC",
1384 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001385}
Doug Zongker96a57e72010-09-26 14:57:41 -07001386
1387def GetTypeAndDevice(mount_point, info):
1388 fstab = info["fstab"]
1389 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001390 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1391 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001392 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001393 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001394
1395
1396def ParseCertificate(data):
1397 """Parse a PEM-format certificate."""
1398 cert = []
1399 save = False
1400 for line in data.split("\n"):
1401 if "--END CERTIFICATE--" in line:
1402 break
1403 if save:
1404 cert.append(line)
1405 if "--BEGIN CERTIFICATE--" in line:
1406 save = True
1407 cert = "".join(cert).decode('base64')
1408 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001409
Doug Zongker412c02f2014-02-13 10:58:24 -08001410def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1411 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001412 """Generate a binary patch that creates the recovery image starting
1413 with the boot image. (Most of the space in these images is just the
1414 kernel, which is identical for the two, so the resulting patch
1415 should be efficient.) Add it to the output zip, along with a shell
1416 script that is run from init.rc on first boot to actually do the
1417 patching and install the new recovery image.
1418
1419 recovery_img and boot_img should be File objects for the
1420 corresponding images. info should be the dictionary returned by
1421 common.LoadInfoDict() on the input target_files.
1422 """
1423
Doug Zongker412c02f2014-02-13 10:58:24 -08001424 if info_dict is None:
1425 info_dict = OPTIONS.info_dict
1426
Tao Baof2cffbd2015-07-22 12:33:18 -07001427 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001428 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001429
Tao Baof2cffbd2015-07-22 12:33:18 -07001430 if full_recovery_image:
1431 output_sink("etc/recovery.img", recovery_img.data)
1432
1433 else:
1434 diff_program = ["imgdiff"]
1435 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1436 if os.path.exists(path):
1437 diff_program.append("-b")
1438 diff_program.append(path)
1439 bonus_args = "-b /system/etc/recovery-resource.dat"
1440 else:
1441 bonus_args = ""
1442
1443 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1444 _, _, patch = d.ComputePatch()
1445 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001446
Dan Albertebb19aa2015-03-27 19:11:53 -07001447 try:
1448 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1449 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1450 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001451 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001452
Tao Baof2cffbd2015-07-22 12:33:18 -07001453 if full_recovery_image:
1454 sh = """#!/system/bin/sh
1455if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1456 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"
1457else
1458 log -t recovery "Recovery image already installed"
1459fi
1460""" % {'type': recovery_type,
1461 'device': recovery_device,
1462 'sha1': recovery_img.sha1,
1463 'size': recovery_img.size}
1464 else:
1465 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001466if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1467 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"
1468else
1469 log -t recovery "Recovery image already installed"
1470fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001471""" % {'boot_size': boot_img.size,
1472 'boot_sha1': boot_img.sha1,
1473 'recovery_size': recovery_img.size,
1474 'recovery_sha1': recovery_img.sha1,
1475 'boot_type': boot_type,
1476 'boot_device': boot_device,
1477 'recovery_type': recovery_type,
1478 'recovery_device': recovery_device,
1479 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001480
1481 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001482 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001483 # target-files expects it to be, and put it there.
1484 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001485 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001486 if system_root_image:
1487 init_rc_dir = os.path.join(input_dir, "ROOT")
1488 else:
1489 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001490 init_rc_files = os.listdir(init_rc_dir)
1491 for init_rc_file in init_rc_files:
1492 if (not init_rc_file.startswith('init.') or
1493 not init_rc_file.endswith('.rc')):
1494 continue
1495
1496 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001497 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001498 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001499 if m:
1500 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001501 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001502 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001503
1504 if found:
1505 break
1506
1507 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001508
1509 output_sink(sh_location, sh)