blob: ca9952d1ec358a9794a14be9e65df249c668965a [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
Tao Baoa6a3aa92015-07-09 11:51:16 -0700105def LoadInfoDict(input_file, input_dir=None):
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
Tao Baocb219822015-07-19 02:38:53 -0700156 # A few properties are stored as links to the files in the out/ directory.
157 # It works fine with the build system. However, they are no longer available
158 # when (re)generating from target_files zip. If input_dir is not None, we
159 # are doing repacking. Redirect those properties to the actual files in the
160 # unzipped directory.
Tao Baoa6a3aa92015-07-09 11:51:16 -0700161 if input_dir is not None:
Tao Baocb219822015-07-19 02:38:53 -0700162 # We carry a copy of file_contexts under META/. If not available, search
163 # BOOT/RAMDISK/. Note that sometimes we may need a different file_contexts
164 # to build images than the one running on device, such as when enabling
165 # system_root_image. In that case, we must have the one for image
166 # generation copied to META/.
Tao Baoa6a3aa92015-07-09 11:51:16 -0700167 fc_config = os.path.join(input_dir, "META", "file_contexts")
Tao Baocb219822015-07-19 02:38:53 -0700168 if d.get("system_root_image") == "true":
169 assert os.path.exists(fc_config)
Tao Baoa6a3aa92015-07-09 11:51:16 -0700170 if not os.path.exists(fc_config):
171 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
172 if not os.path.exists(fc_config):
173 fc_config = None
174
175 if fc_config:
176 d["selinux_fc"] = fc_config
177
Tao Baocb219822015-07-19 02:38:53 -0700178 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
179 if d.get("system_root_image") == "true":
180 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
181 d["ramdisk_fs_config"] = os.path.join(
182 input_dir, "META", "root_filesystem_config.txt")
183
Doug Zongker37974732010-09-16 17:44:38 -0700184 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800185 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700186 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700187 if not line:
188 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700189 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700190 if not value:
191 continue
Doug Zongker37974732010-09-16 17:44:38 -0700192 if name == "blocksize":
193 d[name] = value
194 else:
195 d[name + "_size"] = value
196 except KeyError:
197 pass
198
199 def makeint(key):
200 if key in d:
201 d[key] = int(d[key], 0)
202
203 makeint("recovery_api_version")
204 makeint("blocksize")
205 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700206 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700207 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700208 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700209 makeint("recovery_size")
210 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800211 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700212
Tao Bao448dccb2015-11-19 17:05:46 -0800213 if d.get("no_recovery", False) == "true":
214 d["fstab"] = None
215 else:
216 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
217 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800218 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700219 return d
220
Doug Zongkerc9253822014-02-04 12:17:58 -0800221def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700222 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800223 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700224 except KeyError:
225 print "Warning: could not find SYSTEM/build.prop in %s" % zip
226 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700227 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700228
Michael Runge6e836112014-04-15 17:40:21 -0700229def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700230 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700231 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700232 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700233 if not line or line.startswith("#"):
234 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700235 if "=" in line:
236 name, value = line.split("=", 1)
237 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700238 return d
239
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700240def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700241 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700242 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 self.mount_point = mount_point
244 self.fs_type = fs_type
245 self.device = device
246 self.length = length
247 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700248 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700249
250 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800251 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700252 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800253 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700254 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700255
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 if fstab_version == 1:
257 d = {}
258 for line in data.split("\n"):
259 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700260 if not line or line.startswith("#"):
261 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800262 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700263 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800265 options = None
266 if len(pieces) >= 4:
267 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700268 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269 if len(pieces) >= 5:
270 options = pieces[4]
271 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800273 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800274 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700276
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 mount_point = pieces[0]
278 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279 if options:
280 options = options.split(",")
281 for i in options:
282 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800284 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700285 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800286
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
288 device=pieces[2], length=length,
289 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800290
291 elif fstab_version == 2:
292 d = {}
293 for line in data.split("\n"):
294 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700295 if not line or line.startswith("#"):
296 continue
Tao Bao548eb762015-06-10 12:32:41 -0700297 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800298 pieces = line.split()
299 if len(pieces) != 5:
300 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
301
302 # Ignore entries that are managed by vold
303 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700304 if "voldmanaged=" in options:
305 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800306
307 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700308 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800309 options = options.split(",")
310 for i in options:
311 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700312 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800313 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800314 # Ignore all unknown options in the unified fstab
315 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800316
Tao Bao548eb762015-06-10 12:32:41 -0700317 mount_flags = pieces[3]
318 # Honor the SELinux context if present.
319 context = None
320 for i in mount_flags.split(","):
321 if i.startswith("context="):
322 context = i
323
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 mount_point = pieces[1]
325 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700326 device=pieces[0], length=length,
327 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800328
329 else:
330 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
331
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700332 # / is used for the system mount point when the root directory is included in
Tao Baob11d2c52015-07-21 18:01:20 -0700333 # system. Other areas assume system is always at "/system" so point /system
334 # at /.
Daniel Rosenbergb3b8ce62015-06-05 17:59:27 -0700335 if system_root_image:
336 assert not d.has_key("/system") and d.has_key("/")
337 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700338 return d
339
340
Doug Zongker37974732010-09-16 17:44:38 -0700341def DumpInfoDict(d):
342 for k, v in sorted(d.items()):
343 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700344
Dan Albert8b72aef2015-03-23 19:13:21 -0700345
Tao Baob11d2c52015-07-21 18:01:20 -0700346def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
347 has_ramdisk=False):
348 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700349
Tao Baob11d2c52015-07-21 18:01:20 -0700350 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
351 'sourcedir'), and turn them into a boot image. Return the image data, or
352 None if sourcedir does not appear to contains files for building the
353 requested image."""
354
355 def make_ramdisk():
356 ramdisk_img = tempfile.NamedTemporaryFile()
357
358 if os.access(fs_config_file, os.F_OK):
359 cmd = ["mkbootfs", "-f", fs_config_file,
360 os.path.join(sourcedir, "RAMDISK")]
361 else:
362 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
363 p1 = Run(cmd, stdout=subprocess.PIPE)
364 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
365
366 p2.wait()
367 p1.wait()
368 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
369 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
370
371 return ramdisk_img
372
373 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
374 return None
375
376 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700377 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700378
Doug Zongkerd5131602012-08-02 14:46:42 -0700379 if info_dict is None:
380 info_dict = OPTIONS.info_dict
381
Doug Zongkereef39442009-04-02 12:14:19 -0700382 img = tempfile.NamedTemporaryFile()
383
Tao Baob11d2c52015-07-21 18:01:20 -0700384 if has_ramdisk:
385 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700386
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800387 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
388 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
389
390 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700391
Benoit Fradina45a8682014-07-14 21:00:43 +0200392 fn = os.path.join(sourcedir, "second")
393 if os.access(fn, os.F_OK):
394 cmd.append("--second")
395 cmd.append(fn)
396
Doug Zongker171f1cd2009-06-15 22:36:37 -0700397 fn = os.path.join(sourcedir, "cmdline")
398 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700399 cmd.append("--cmdline")
400 cmd.append(open(fn).read().rstrip("\n"))
401
402 fn = os.path.join(sourcedir, "base")
403 if os.access(fn, os.F_OK):
404 cmd.append("--base")
405 cmd.append(open(fn).read().rstrip("\n"))
406
Ying Wang4de6b5b2010-08-25 14:29:34 -0700407 fn = os.path.join(sourcedir, "pagesize")
408 if os.access(fn, os.F_OK):
409 cmd.append("--pagesize")
410 cmd.append(open(fn).read().rstrip("\n"))
411
Doug Zongkerd5131602012-08-02 14:46:42 -0700412 args = info_dict.get("mkbootimg_args", None)
413 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700414 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700415
Tao Baob11d2c52015-07-21 18:01:20 -0700416 if has_ramdisk:
417 cmd.extend(["--ramdisk", ramdisk_img.name])
418
Tao Baod95e9fd2015-03-29 23:07:41 -0700419 img_unsigned = None
420 if info_dict.get("vboot", None):
421 img_unsigned = tempfile.NamedTemporaryFile()
Tao Baob11d2c52015-07-21 18:01:20 -0700422 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700423 else:
Tao Baob11d2c52015-07-21 18:01:20 -0700424 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700425
426 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700427 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700428 assert p.returncode == 0, "mkbootimg of %s image failed" % (
429 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700430
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100431 if (info_dict.get("boot_signer", None) == "true" and
432 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700433 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700434 cmd = [OPTIONS.boot_signer_path]
435 cmd.extend(OPTIONS.boot_signer_args)
436 cmd.extend([path, img.name,
437 info_dict["verity_key"] + ".pk8",
438 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700439 p = Run(cmd, stdout=subprocess.PIPE)
440 p.communicate()
441 assert p.returncode == 0, "boot_signer of %s image failed" % path
442
Tao Baod95e9fd2015-03-29 23:07:41 -0700443 # Sign the image if vboot is non-empty.
444 elif info_dict.get("vboot", None):
445 path = "/" + os.path.basename(sourcedir).lower()
446 img_keyblock = tempfile.NamedTemporaryFile()
447 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
448 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700449 info_dict["vboot_key"] + ".vbprivk",
450 info_dict["vboot_subkey"] + ".vbprivk",
451 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700452 img.name]
453 p = Run(cmd, stdout=subprocess.PIPE)
454 p.communicate()
455 assert p.returncode == 0, "vboot_signer of %s image failed" % path
456
Tao Baof3282b42015-04-01 11:21:55 -0700457 # Clean up the temp files.
458 img_unsigned.close()
459 img_keyblock.close()
460
Doug Zongkereef39442009-04-02 12:14:19 -0700461 img.seek(os.SEEK_SET, 0)
462 data = img.read()
463
Tao Baob11d2c52015-07-21 18:01:20 -0700464 if has_ramdisk:
465 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700466 img.close()
467
468 return data
469
470
Doug Zongkerd5131602012-08-02 14:46:42 -0700471def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
472 info_dict=None):
Tao Baob11d2c52015-07-21 18:01:20 -0700473 """Return a File object with the desired bootable image.
474
475 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
476 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
477 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700478
Doug Zongker55d93282011-01-25 17:03:34 -0800479 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
480 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700481 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800482 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700483
484 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
485 if os.path.exists(prebuilt_path):
486 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
487 return File.FromLocalFile(name, prebuilt_path)
488
489 print "building image from target_files %s..." % (tree_subdir,)
Tao Baob11d2c52015-07-21 18:01:20 -0700490
491 if info_dict is None:
492 info_dict = OPTIONS.info_dict
493
494 # With system_root_image == "true", we don't pack ramdisk into the boot image.
495 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
496 prebuilt_name != "boot.img")
497
Doug Zongker6f1d0312014-08-22 08:07:12 -0700498 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Baob11d2c52015-07-21 18:01:20 -0700499 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
500 os.path.join(unpack_dir, fs_config),
501 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700502 if data:
503 return File(name, data)
504 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800505
Doug Zongkereef39442009-04-02 12:14:19 -0700506
Doug Zongker75f17362009-12-08 13:46:44 -0800507def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800508 """Unzip the given archive into a temporary directory and return the name.
509
510 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
511 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
512
513 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
514 main file), open for reading.
515 """
Doug Zongkereef39442009-04-02 12:14:19 -0700516
517 tmp = tempfile.mkdtemp(prefix="targetfiles-")
518 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800519
520 def unzip_to_dir(filename, dirname):
521 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
522 if pattern is not None:
523 cmd.append(pattern)
524 p = Run(cmd, stdout=subprocess.PIPE)
525 p.communicate()
526 if p.returncode != 0:
527 raise ExternalError("failed to unzip input target-files \"%s\"" %
528 (filename,))
529
530 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
531 if m:
532 unzip_to_dir(m.group(1), tmp)
533 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
534 filename = m.group(1)
535 else:
536 unzip_to_dir(filename, tmp)
537
538 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700539
540
541def GetKeyPasswords(keylist):
542 """Given a list of keys, prompt the user to enter passwords for
543 those which require them. Return a {key: password} dict. password
544 will be None if the key has no password."""
545
Doug Zongker8ce7c252009-05-22 13:34:54 -0700546 no_passwords = []
547 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700548 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700549 devnull = open("/dev/null", "w+b")
550 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800551 # We don't need a password for things that aren't really keys.
552 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700553 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700554 continue
555
T.R. Fullhart37e10522013-03-18 10:31:26 -0700556 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700557 "-inform", "DER", "-nocrypt"],
558 stdin=devnull.fileno(),
559 stdout=devnull.fileno(),
560 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700561 p.communicate()
562 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700563 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700564 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700565 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700566 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
567 "-inform", "DER", "-passin", "pass:"],
568 stdin=devnull.fileno(),
569 stdout=devnull.fileno(),
570 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700571 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700572 if p.returncode == 0:
573 # Encrypted key with empty string as password.
574 key_passwords[k] = ''
575 elif stderr.startswith('Error decrypting key'):
576 # Definitely encrypted key.
577 # It would have said "Error reading key" if it didn't parse correctly.
578 need_passwords.append(k)
579 else:
580 # Potentially, a type of key that openssl doesn't understand.
581 # We'll let the routines in signapk.jar handle it.
582 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700583 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700584
T.R. Fullhart37e10522013-03-18 10:31:26 -0700585 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700586 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700587 return key_passwords
588
589
Doug Zongker951495f2009-08-14 12:44:19 -0700590def SignFile(input_name, output_name, key, password, align=None,
591 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700592 """Sign the input_name zip/jar/apk, producing output_name. Use the
593 given key and password (the latter may be None if the key does not
594 have a password.
595
596 If align is an integer > 1, zipalign is run to align stored files in
597 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700598
599 If whole_file is true, use the "-w" option to SignApk to embed a
600 signature that covers the whole file in the archive comment of the
601 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700602 """
Doug Zongker951495f2009-08-14 12:44:19 -0700603
Doug Zongkereef39442009-04-02 12:14:19 -0700604 if align == 0 or align == 1:
605 align = None
606
607 if align:
608 temp = tempfile.NamedTemporaryFile()
609 sign_name = temp.name
610 else:
611 sign_name = output_name
612
Baligh Uddin339ee492014-09-05 11:18:07 -0700613 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700614 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
615 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700616 if whole_file:
617 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700618 cmd.extend([key + OPTIONS.public_key_suffix,
619 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700620 input_name, sign_name])
621
622 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700623 if password is not None:
624 password += "\n"
625 p.communicate(password)
626 if p.returncode != 0:
627 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
628
629 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700630 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700631 p.communicate()
632 if p.returncode != 0:
633 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
634 temp.close()
635
636
Doug Zongker37974732010-09-16 17:44:38 -0700637def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700638 """Check the data string passed against the max size limit, if
639 any, for the given target. Raise exception if the data is too big.
640 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700641
Dan Albert8b72aef2015-03-23 19:13:21 -0700642 if target.endswith(".img"):
643 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700644 mount_point = "/" + target
645
Ying Wangf8824af2014-06-03 14:07:27 -0700646 fs_type = None
647 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700648 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700649 if mount_point == "/userdata":
650 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700651 p = info_dict["fstab"][mount_point]
652 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800653 device = p.device
654 if "/" in device:
655 device = device[device.rfind("/")+1:]
656 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700657 if not fs_type or not limit:
658 return
Doug Zongkereef39442009-04-02 12:14:19 -0700659
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700660 if fs_type == "yaffs2":
661 # image size should be increased by 1/64th to account for the
662 # spare area (64 bytes per 2k page)
663 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800664 size = len(data)
665 pct = float(size) * 100.0 / limit
666 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
667 if pct >= 99.0:
668 raise ExternalError(msg)
669 elif pct >= 95.0:
670 print
671 print " WARNING: ", msg
672 print
673 elif OPTIONS.verbose:
674 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700675
676
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800677def ReadApkCerts(tf_zip):
678 """Given a target_files ZipFile, parse the META/apkcerts.txt file
679 and return a {package: cert} dict."""
680 certmap = {}
681 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
682 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700683 if not line:
684 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800685 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
686 r'private_key="(.*)"$', line)
687 if m:
688 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700689 public_key_suffix_len = len(OPTIONS.public_key_suffix)
690 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800691 if cert in SPECIAL_CERT_STRINGS and not privkey:
692 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700693 elif (cert.endswith(OPTIONS.public_key_suffix) and
694 privkey.endswith(OPTIONS.private_key_suffix) and
695 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
696 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800697 else:
698 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
699 return certmap
700
701
Doug Zongkereef39442009-04-02 12:14:19 -0700702COMMON_DOCSTRING = """
703 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700704 Prepend <dir>/bin to the list of places to search for binaries
705 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700706
Doug Zongker05d3dea2009-06-22 11:32:31 -0700707 -s (--device_specific) <file>
708 Path to the python module containing device-specific
709 releasetools code.
710
Doug Zongker8bec09e2009-11-30 15:37:14 -0800711 -x (--extra) <key=value>
712 Add a key/value pair to the 'extras' dict, which device-specific
713 extension code may look at.
714
Doug Zongkereef39442009-04-02 12:14:19 -0700715 -v (--verbose)
716 Show command lines being executed.
717
718 -h (--help)
719 Display this usage message and exit.
720"""
721
722def Usage(docstring):
723 print docstring.rstrip("\n")
724 print COMMON_DOCSTRING
725
726
727def ParseOptions(argv,
728 docstring,
729 extra_opts="", extra_long_opts=(),
730 extra_option_handler=None):
731 """Parse the options in argv and return any arguments that aren't
732 flags. docstring is the calling module's docstring, to be displayed
733 for errors and -h. extra_opts and extra_long_opts are for flags
734 defined by the caller, which are processed by passing them to
735 extra_option_handler."""
736
737 try:
738 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800739 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700740 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700741 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700742 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
743 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800744 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700745 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700746 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700747 Usage(docstring)
748 print "**", str(err), "**"
749 sys.exit(2)
750
Doug Zongkereef39442009-04-02 12:14:19 -0700751 for o, a in opts:
752 if o in ("-h", "--help"):
753 Usage(docstring)
754 sys.exit()
755 elif o in ("-v", "--verbose"):
756 OPTIONS.verbose = True
757 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700758 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700759 elif o in ("--signapk_path",):
760 OPTIONS.signapk_path = a
761 elif o in ("--extra_signapk_args",):
762 OPTIONS.extra_signapk_args = shlex.split(a)
763 elif o in ("--java_path",):
764 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700765 elif o in ("--java_args",):
766 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700767 elif o in ("--public_key_suffix",):
768 OPTIONS.public_key_suffix = a
769 elif o in ("--private_key_suffix",):
770 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800771 elif o in ("--boot_signer_path",):
772 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700773 elif o in ("--boot_signer_args",):
774 OPTIONS.boot_signer_args = shlex.split(a)
775 elif o in ("--verity_signer_path",):
776 OPTIONS.verity_signer_path = a
777 elif o in ("--verity_signer_args",):
778 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700779 elif o in ("-s", "--device_specific"):
780 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800781 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800782 key, value = a.split("=", 1)
783 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700784 else:
785 if extra_option_handler is None or not extra_option_handler(o, a):
786 assert False, "unknown option \"%s\"" % (o,)
787
Doug Zongker85448772014-09-09 14:59:20 -0700788 if OPTIONS.search_path:
789 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
790 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700791
792 return args
793
794
Doug Zongkerfc44a512014-08-26 13:10:25 -0700795def MakeTempFile(prefix=None, suffix=None):
796 """Make a temp file and add it to the list of things to be deleted
797 when Cleanup() is called. Return the filename."""
798 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
799 os.close(fd)
800 OPTIONS.tempfiles.append(fn)
801 return fn
802
803
Doug Zongkereef39442009-04-02 12:14:19 -0700804def Cleanup():
805 for i in OPTIONS.tempfiles:
806 if os.path.isdir(i):
807 shutil.rmtree(i)
808 else:
809 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700810
811
812class PasswordManager(object):
813 def __init__(self):
814 self.editor = os.getenv("EDITOR", None)
815 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
816
817 def GetPasswords(self, items):
818 """Get passwords corresponding to each string in 'items',
819 returning a dict. (The dict may have keys in addition to the
820 values in 'items'.)
821
822 Uses the passwords in $ANDROID_PW_FILE if available, letting the
823 user edit that file to add more needed passwords. If no editor is
824 available, or $ANDROID_PW_FILE isn't define, prompts the user
825 interactively in the ordinary way.
826 """
827
828 current = self.ReadFile()
829
830 first = True
831 while True:
832 missing = []
833 for i in items:
834 if i not in current or not current[i]:
835 missing.append(i)
836 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700837 if not missing:
838 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700839
840 for i in missing:
841 current[i] = ""
842
843 if not first:
844 print "key file %s still missing some passwords." % (self.pwfile,)
845 answer = raw_input("try to edit again? [y]> ").strip()
846 if answer and answer[0] not in 'yY':
847 raise RuntimeError("key passwords unavailable")
848 first = False
849
850 current = self.UpdateAndReadFile(current)
851
Dan Albert8b72aef2015-03-23 19:13:21 -0700852 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700853 """Prompt the user to enter a value (password) for each key in
854 'current' whose value is fales. Returns a new dict with all the
855 values.
856 """
857 result = {}
858 for k, v in sorted(current.iteritems()):
859 if v:
860 result[k] = v
861 else:
862 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700863 result[k] = getpass.getpass(
864 "Enter password for %s key> " % k).strip()
865 if result[k]:
866 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700867 return result
868
869 def UpdateAndReadFile(self, current):
870 if not self.editor or not self.pwfile:
871 return self.PromptResult(current)
872
873 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700874 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700875 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
876 f.write("# (Additional spaces are harmless.)\n\n")
877
878 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700879 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
880 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700881 f.write("[[[ %s ]]] %s\n" % (v, k))
882 if not v and first_line is None:
883 # position cursor on first line with no password.
884 first_line = i + 4
885 f.close()
886
887 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
888 _, _ = p.communicate()
889
890 return self.ReadFile()
891
892 def ReadFile(self):
893 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700894 if self.pwfile is None:
895 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700896 try:
897 f = open(self.pwfile, "r")
898 for line in f:
899 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700900 if not line or line[0] == '#':
901 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700902 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
903 if not m:
904 print "failed to parse password file: ", line
905 else:
906 result[m.group(2)] = m.group(1)
907 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700908 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700909 if e.errno != errno.ENOENT:
910 print "error reading password file: ", str(e)
911 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700912
913
Dan Albert8e0178d2015-01-27 15:53:15 -0800914def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
915 compress_type=None):
916 import datetime
917
918 # http://b/18015246
919 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
920 # for files larger than 2GiB. We can work around this by adjusting their
921 # limit. Note that `zipfile.writestr()` will not work for strings larger than
922 # 2GiB. The Python interpreter sometimes rejects strings that large (though
923 # it isn't clear to me exactly what circumstances cause this).
924 # `zipfile.write()` must be used directly to work around this.
925 #
926 # This mess can be avoided if we port to python3.
927 saved_zip64_limit = zipfile.ZIP64_LIMIT
928 zipfile.ZIP64_LIMIT = (1 << 32) - 1
929
930 if compress_type is None:
931 compress_type = zip_file.compression
932 if arcname is None:
933 arcname = filename
934
935 saved_stat = os.stat(filename)
936
937 try:
938 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
939 # file to be zipped and reset it when we're done.
940 os.chmod(filename, perms)
941
942 # Use a fixed timestamp so the output is repeatable.
943 epoch = datetime.datetime.fromtimestamp(0)
944 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
945 os.utime(filename, (timestamp, timestamp))
946
947 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
948 finally:
949 os.chmod(filename, saved_stat.st_mode)
950 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
951 zipfile.ZIP64_LIMIT = saved_zip64_limit
952
953
Tao Bao58c1b962015-05-20 09:32:18 -0700954def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700955 compress_type=None):
956 """Wrap zipfile.writestr() function to work around the zip64 limit.
957
958 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
959 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
960 when calling crc32(bytes).
961
962 But it still works fine to write a shorter string into a large zip file.
963 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
964 when we know the string won't be too long.
965 """
966
967 saved_zip64_limit = zipfile.ZIP64_LIMIT
968 zipfile.ZIP64_LIMIT = (1 << 32) - 1
969
970 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
971 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700972 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700973 if perms is None:
974 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800975 else:
Tao Baof3282b42015-04-01 11:21:55 -0700976 zinfo = zinfo_or_arcname
977
978 # If compress_type is given, it overrides the value in zinfo.
979 if compress_type is not None:
980 zinfo.compress_type = compress_type
981
Tao Bao58c1b962015-05-20 09:32:18 -0700982 # If perms is given, it has a priority.
983 if perms is not None:
984 zinfo.external_attr = perms << 16
985
Tao Baof3282b42015-04-01 11:21:55 -0700986 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700987 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
988
Dan Albert8b72aef2015-03-23 19:13:21 -0700989 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700990 zipfile.ZIP64_LIMIT = saved_zip64_limit
991
992
993def ZipClose(zip_file):
994 # http://b/18015246
995 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
996 # central directory.
997 saved_zip64_limit = zipfile.ZIP64_LIMIT
998 zipfile.ZIP64_LIMIT = (1 << 32) - 1
999
1000 zip_file.close()
1001
1002 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001003
1004
1005class DeviceSpecificParams(object):
1006 module = None
1007 def __init__(self, **kwargs):
1008 """Keyword arguments to the constructor become attributes of this
1009 object, which is passed to all functions in the device-specific
1010 module."""
1011 for k, v in kwargs.iteritems():
1012 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001013 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001014
1015 if self.module is None:
1016 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001017 if not path:
1018 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001019 try:
1020 if os.path.isdir(path):
1021 info = imp.find_module("releasetools", [path])
1022 else:
1023 d, f = os.path.split(path)
1024 b, x = os.path.splitext(f)
1025 if x == ".py":
1026 f = b
1027 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001028 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001029 self.module = imp.load_module("device_specific", *info)
1030 except ImportError:
1031 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001032
1033 def _DoCall(self, function_name, *args, **kwargs):
1034 """Call the named function in the device-specific module, passing
1035 the given args and kwargs. The first argument to the call will be
1036 the DeviceSpecific object itself. If there is no module, or the
1037 module does not define the function, return the value of the
1038 'default' kwarg (which itself defaults to None)."""
1039 if self.module is None or not hasattr(self.module, function_name):
1040 return kwargs.get("default", None)
1041 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1042
1043 def FullOTA_Assertions(self):
1044 """Called after emitting the block of assertions at the top of a
1045 full OTA package. Implementations can add whatever additional
1046 assertions they like."""
1047 return self._DoCall("FullOTA_Assertions")
1048
Doug Zongkere5ff5902012-01-17 10:55:37 -08001049 def FullOTA_InstallBegin(self):
1050 """Called at the start of full OTA installation."""
1051 return self._DoCall("FullOTA_InstallBegin")
1052
Doug Zongker05d3dea2009-06-22 11:32:31 -07001053 def FullOTA_InstallEnd(self):
1054 """Called at the end of full OTA installation; typically this is
1055 used to install the image for the device's baseband processor."""
1056 return self._DoCall("FullOTA_InstallEnd")
1057
1058 def IncrementalOTA_Assertions(self):
1059 """Called after emitting the block of assertions at the top of an
1060 incremental OTA package. Implementations can add whatever
1061 additional assertions they like."""
1062 return self._DoCall("IncrementalOTA_Assertions")
1063
Doug Zongkere5ff5902012-01-17 10:55:37 -08001064 def IncrementalOTA_VerifyBegin(self):
1065 """Called at the start of the verification phase of incremental
1066 OTA installation; additional checks can be placed here to abort
1067 the script before any changes are made."""
1068 return self._DoCall("IncrementalOTA_VerifyBegin")
1069
Doug Zongker05d3dea2009-06-22 11:32:31 -07001070 def IncrementalOTA_VerifyEnd(self):
1071 """Called at the end of the verification phase of incremental OTA
1072 installation; additional checks can be placed here to abort the
1073 script before any changes are made."""
1074 return self._DoCall("IncrementalOTA_VerifyEnd")
1075
Doug Zongkere5ff5902012-01-17 10:55:37 -08001076 def IncrementalOTA_InstallBegin(self):
1077 """Called at the start of incremental OTA installation (after
1078 verification is complete)."""
1079 return self._DoCall("IncrementalOTA_InstallBegin")
1080
Doug Zongker05d3dea2009-06-22 11:32:31 -07001081 def IncrementalOTA_InstallEnd(self):
1082 """Called at the end of incremental OTA installation; typically
1083 this is used to install the image for the device's baseband
1084 processor."""
1085 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001086
1087class File(object):
1088 def __init__(self, name, data):
1089 self.name = name
1090 self.data = data
1091 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001092 self.sha1 = sha1(data).hexdigest()
1093
1094 @classmethod
1095 def FromLocalFile(cls, name, diskname):
1096 f = open(diskname, "rb")
1097 data = f.read()
1098 f.close()
1099 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001100
1101 def WriteToTemp(self):
1102 t = tempfile.NamedTemporaryFile()
1103 t.write(self.data)
1104 t.flush()
1105 return t
1106
Geremy Condra36bd3652014-02-06 19:45:10 -08001107 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001108 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001109
1110DIFF_PROGRAM_BY_EXT = {
1111 ".gz" : "imgdiff",
1112 ".zip" : ["imgdiff", "-z"],
1113 ".jar" : ["imgdiff", "-z"],
1114 ".apk" : ["imgdiff", "-z"],
1115 ".img" : "imgdiff",
1116 }
1117
1118class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001119 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001120 self.tf = tf
1121 self.sf = sf
1122 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001123 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001124
1125 def ComputePatch(self):
1126 """Compute the patch (as a string of data) needed to turn sf into
1127 tf. Returns the same tuple as GetPatch()."""
1128
1129 tf = self.tf
1130 sf = self.sf
1131
Doug Zongker24cd2802012-08-14 16:36:15 -07001132 if self.diff_program:
1133 diff_program = self.diff_program
1134 else:
1135 ext = os.path.splitext(tf.name)[1]
1136 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001137
1138 ttemp = tf.WriteToTemp()
1139 stemp = sf.WriteToTemp()
1140
1141 ext = os.path.splitext(tf.name)[1]
1142
1143 try:
1144 ptemp = tempfile.NamedTemporaryFile()
1145 if isinstance(diff_program, list):
1146 cmd = copy.copy(diff_program)
1147 else:
1148 cmd = [diff_program]
1149 cmd.append(stemp.name)
1150 cmd.append(ttemp.name)
1151 cmd.append(ptemp.name)
1152 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001153 err = []
1154 def run():
1155 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001156 if e:
1157 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001158 th = threading.Thread(target=run)
1159 th.start()
1160 th.join(timeout=300) # 5 mins
1161 if th.is_alive():
1162 print "WARNING: diff command timed out"
1163 p.terminate()
1164 th.join(5)
1165 if th.is_alive():
1166 p.kill()
1167 th.join()
1168
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001169 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001170 print "WARNING: failure running %s:\n%s\n" % (
1171 diff_program, "".join(err))
1172 self.patch = None
1173 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001174 diff = ptemp.read()
1175 finally:
1176 ptemp.close()
1177 stemp.close()
1178 ttemp.close()
1179
1180 self.patch = diff
1181 return self.tf, self.sf, self.patch
1182
1183
1184 def GetPatch(self):
1185 """Return a tuple (target_file, source_file, patch_data).
1186 patch_data may be None if ComputePatch hasn't been called, or if
1187 computing the patch failed."""
1188 return self.tf, self.sf, self.patch
1189
1190
1191def ComputeDifferences(diffs):
1192 """Call ComputePatch on all the Difference objects in 'diffs'."""
1193 print len(diffs), "diffs to compute"
1194
1195 # Do the largest files first, to try and reduce the long-pole effect.
1196 by_size = [(i.tf.size, i) for i in diffs]
1197 by_size.sort(reverse=True)
1198 by_size = [i[1] for i in by_size]
1199
1200 lock = threading.Lock()
1201 diff_iter = iter(by_size) # accessed under lock
1202
1203 def worker():
1204 try:
1205 lock.acquire()
1206 for d in diff_iter:
1207 lock.release()
1208 start = time.time()
1209 d.ComputePatch()
1210 dur = time.time() - start
1211 lock.acquire()
1212
1213 tf, sf, patch = d.GetPatch()
1214 if sf.name == tf.name:
1215 name = tf.name
1216 else:
1217 name = "%s (%s)" % (tf.name, sf.name)
1218 if patch is None:
1219 print "patching failed! %s" % (name,)
1220 else:
1221 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1222 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1223 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001224 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001225 print e
1226 raise
1227
1228 # start worker threads; wait for them all to finish.
1229 threads = [threading.Thread(target=worker)
1230 for i in range(OPTIONS.worker_threads)]
1231 for th in threads:
1232 th.start()
1233 while threads:
1234 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001235
1236
Dan Albert8b72aef2015-03-23 19:13:21 -07001237class BlockDifference(object):
1238 def __init__(self, partition, tgt, src=None, check_first_block=False,
1239 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001240 self.tgt = tgt
1241 self.src = src
1242 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001243 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001244
Tao Bao5ece99d2015-05-12 11:42:31 -07001245 # Due to http://b/20939131, check_first_block is disabled temporarily.
1246 assert not self.check_first_block
1247
Tao Baodd2a5892015-03-12 12:32:37 -07001248 if version is None:
1249 version = 1
1250 if OPTIONS.info_dict:
1251 version = max(
1252 int(i) for i in
1253 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1254 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001255
1256 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001257 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001258 tmpdir = tempfile.mkdtemp()
1259 OPTIONS.tempfiles.append(tmpdir)
1260 self.path = os.path.join(tmpdir, partition)
1261 b.Compute(self.path)
1262
Tao Baoe09359a2015-10-13 16:37:12 -07001263 if src is None:
1264 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1265 else:
1266 _, self.device = GetTypeAndDevice("/" + partition,
1267 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001268
1269 def WriteScript(self, script, output_zip, progress=None):
1270 if not self.src:
1271 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001272 script.Print("Patching %s image unconditionally..." % (self.partition,))
1273 else:
1274 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001275
Dan Albert8b72aef2015-03-23 19:13:21 -07001276 if progress:
1277 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001278 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001279 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001280
1281 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001282 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001283 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001284 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001285 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001286 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1287 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001288 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001289 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1290 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001291 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001292 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001293 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001294 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001295 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001296 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001297 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001298 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001299 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001300
Tao Baodd2a5892015-03-12 12:32:37 -07001301 # When generating incrementals for the system and vendor partitions,
1302 # explicitly check the first block (which contains the superblock) of
1303 # the partition to see if it's what we expect. If this check fails,
1304 # give an explicit log message about the partition having been
1305 # remounted R/W (the most likely explanation) and the need to flash to
1306 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001307 if self.check_first_block:
1308 self._CheckFirstBlock(script)
1309
Tao Baodd2a5892015-03-12 12:32:37 -07001310 # Abort the OTA update. Note that the incremental OTA cannot be applied
1311 # even if it may match the checksum of the target partition.
1312 # a) If version < 3, operations like move and erase will make changes
1313 # unconditionally and damage the partition.
1314 # b) If version >= 3, it won't even reach here.
1315 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1316 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001317
Tao Bao5fcaaef2015-06-01 13:40:49 -07001318 def _WritePostInstallVerifyScript(self, script):
1319 partition = self.partition
1320 script.Print('Verifying the updated %s image...' % (partition,))
1321 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1322 ranges = self.tgt.care_map
1323 ranges_str = ranges.to_string_raw()
1324 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1325 self.device, ranges_str,
1326 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001327
1328 # Bug: 20881595
1329 # Verify that extended blocks are really zeroed out.
1330 if self.tgt.extended:
1331 ranges_str = self.tgt.extended.to_string_raw()
1332 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1333 self.device, ranges_str,
1334 self._HashZeroBlocks(self.tgt.extended.size())))
1335 script.Print('Verified the updated %s image.' % (partition,))
1336 script.AppendExtra(
1337 'else\n'
1338 ' abort("%s partition has unexpected non-zero contents after OTA '
1339 'update");\n'
1340 'endif;' % (partition,))
1341 else:
1342 script.Print('Verified the updated %s image.' % (partition,))
1343
Tao Bao5fcaaef2015-06-01 13:40:49 -07001344 script.AppendExtra(
1345 'else\n'
1346 ' abort("%s partition has unexpected contents after OTA update");\n'
1347 'endif;' % (partition,))
1348
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001349 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001350 ZipWrite(output_zip,
1351 '{}.transfer.list'.format(self.path),
1352 '{}.transfer.list'.format(self.partition))
1353 ZipWrite(output_zip,
1354 '{}.new.dat'.format(self.path),
1355 '{}.new.dat'.format(self.partition))
1356 ZipWrite(output_zip,
1357 '{}.patch.dat'.format(self.path),
1358 '{}.patch.dat'.format(self.partition),
1359 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001360
Dan Albert8e0178d2015-01-27 15:53:15 -08001361 call = ('block_image_update("{device}", '
1362 'package_extract_file("{partition}.transfer.list"), '
1363 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1364 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001365 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001366
Dan Albert8b72aef2015-03-23 19:13:21 -07001367 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001368 data = source.ReadRangeSet(ranges)
1369 ctx = sha1()
1370
1371 for p in data:
1372 ctx.update(p)
1373
1374 return ctx.hexdigest()
1375
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001376 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1377 """Return the hash value for all zero blocks."""
1378 zero_block = '\x00' * 4096
1379 ctx = sha1()
1380 for _ in range(num_blocks):
1381 ctx.update(zero_block)
1382
1383 return ctx.hexdigest()
1384
Tao Bao5ece99d2015-05-12 11:42:31 -07001385 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1386 # remounting R/W. Will change the checking to a finer-grained way to
1387 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001388 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001389 r = rangelib.RangeSet((0, 1))
1390 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001391
1392 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1393 'abort("%s has been remounted R/W; '
1394 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001395 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001396 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001397
1398DataImage = blockimgdiff.DataImage
1399
1400
Doug Zongker96a57e72010-09-26 14:57:41 -07001401# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001402PARTITION_TYPES = {
1403 "yaffs2": "MTD",
1404 "mtd": "MTD",
1405 "ext4": "EMMC",
1406 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001407 "f2fs": "EMMC",
1408 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001409}
Doug Zongker96a57e72010-09-26 14:57:41 -07001410
1411def GetTypeAndDevice(mount_point, info):
1412 fstab = info["fstab"]
1413 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001414 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1415 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001416 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001417 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001418
1419
1420def ParseCertificate(data):
1421 """Parse a PEM-format certificate."""
1422 cert = []
1423 save = False
1424 for line in data.split("\n"):
1425 if "--END CERTIFICATE--" in line:
1426 break
1427 if save:
1428 cert.append(line)
1429 if "--BEGIN CERTIFICATE--" in line:
1430 save = True
1431 cert = "".join(cert).decode('base64')
1432 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001433
Doug Zongker412c02f2014-02-13 10:58:24 -08001434def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1435 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001436 """Generate a binary patch that creates the recovery image starting
1437 with the boot image. (Most of the space in these images is just the
1438 kernel, which is identical for the two, so the resulting patch
1439 should be efficient.) Add it to the output zip, along with a shell
1440 script that is run from init.rc on first boot to actually do the
1441 patching and install the new recovery image.
1442
1443 recovery_img and boot_img should be File objects for the
1444 corresponding images. info should be the dictionary returned by
1445 common.LoadInfoDict() on the input target_files.
1446 """
1447
Doug Zongker412c02f2014-02-13 10:58:24 -08001448 if info_dict is None:
1449 info_dict = OPTIONS.info_dict
1450
Tao Bao6ed14912015-07-22 12:33:18 -07001451 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Baob11d2c52015-07-21 18:01:20 -07001452 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001453
Tao Bao6ed14912015-07-22 12:33:18 -07001454 if full_recovery_image:
1455 output_sink("etc/recovery.img", recovery_img.data)
1456
1457 else:
1458 diff_program = ["imgdiff"]
1459 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1460 if os.path.exists(path):
1461 diff_program.append("-b")
1462 diff_program.append(path)
1463 bonus_args = "-b /system/etc/recovery-resource.dat"
1464 else:
1465 bonus_args = ""
1466
1467 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1468 _, _, patch = d.ComputePatch()
1469 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001470
Dan Albertebb19aa2015-03-27 19:11:53 -07001471 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001472 # The following GetTypeAndDevice()s need to use the path in the target
1473 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001474 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1475 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1476 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001477 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001478
Tao Bao6ed14912015-07-22 12:33:18 -07001479 if full_recovery_image:
1480 sh = """#!/system/bin/sh
1481if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1482 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"
1483else
1484 log -t recovery "Recovery image already installed"
1485fi
1486""" % {'type': recovery_type,
1487 'device': recovery_device,
1488 'sha1': recovery_img.sha1,
1489 'size': recovery_img.size}
1490 else:
1491 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001492if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1493 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"
1494else
1495 log -t recovery "Recovery image already installed"
1496fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001497""" % {'boot_size': boot_img.size,
1498 'boot_sha1': boot_img.sha1,
1499 'recovery_size': recovery_img.size,
1500 'recovery_sha1': recovery_img.sha1,
1501 'boot_type': boot_type,
1502 'boot_device': boot_device,
1503 'recovery_type': recovery_type,
1504 'recovery_device': recovery_device,
1505 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001506
1507 # The install script location moved from /system/etc to /system/bin
Tao Bao610754e2015-07-07 18:31:47 -07001508 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001509 # target-files expects it to be, and put it there.
1510 sh_location = "etc/install-recovery.sh"
Tao Bao610754e2015-07-07 18:31:47 -07001511 found = False
Tao Baob11d2c52015-07-21 18:01:20 -07001512 if system_root_image:
1513 init_rc_dir = os.path.join(input_dir, "ROOT")
1514 else:
1515 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao610754e2015-07-07 18:31:47 -07001516 init_rc_files = os.listdir(init_rc_dir)
1517 for init_rc_file in init_rc_files:
1518 if (not init_rc_file.startswith('init.') or
1519 not init_rc_file.endswith('.rc')):
1520 continue
Tao Bao610754e2015-07-07 18:31:47 -07001521 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001522 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001523 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001524 if m:
1525 sh_location = m.group(1)
Tao Bao610754e2015-07-07 18:31:47 -07001526 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001527 break
Tao Bao610754e2015-07-07 18:31:47 -07001528 if found:
1529 break
Tao Bao610754e2015-07-07 18:31:47 -07001530 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001531
1532 output_sink(sh_location, sh)