blob: 372ec9383a82af0cfbeb4f2f1fa37f6655e8268a [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
62 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070063 # Stash size cannot exceed cache_size * threshold.
64 self.cache_size = None
65 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070066
67
68OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070069
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080070
71# Values for "certificate" in apkcerts that mean special things.
72SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
73
74
Dan Albert8b72aef2015-03-23 19:13:21 -070075class ExternalError(RuntimeError):
76 pass
Doug Zongkereef39442009-04-02 12:14:19 -070077
78
79def Run(args, **kwargs):
80 """Create and return a subprocess.Popen object, printing the command
81 line on the terminal if -v was specified."""
82 if OPTIONS.verbose:
83 print " running: ", " ".join(args)
84 return subprocess.Popen(args, **kwargs)
85
86
Ying Wang7e6d4e42010-12-13 16:25:36 -080087def CloseInheritedPipes():
88 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
89 before doing other work."""
90 if platform.system() != "Darwin":
91 return
92 for d in range(3, 1025):
93 try:
94 stat = os.fstat(d)
95 if stat is not None:
96 pipebit = stat[0] & 0x1000
97 if pipebit != 0:
98 os.close(d)
99 except OSError:
100 pass
101
102
Tao Bao2c15d9e2015-07-09 11:51:16 -0700103def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700104 """Read and parse the META/misc_info.txt key/value pairs from the
105 input target files and return a dict."""
106
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 if isinstance(input_file, zipfile.ZipFile):
109 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700111 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800112 try:
113 with open(path) as f:
114 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700115 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800116 if e.errno == errno.ENOENT:
117 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118 d = {}
119 try:
Michael Runge6e836112014-04-15 17:40:21 -0700120 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700121 except KeyError:
122 # ok if misc_info.txt doesn't exist
123 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700124
Doug Zongker37974732010-09-16 17:44:38 -0700125 # backwards compatibility: These values used to be in their own
126 # files. Look for them, in case we're processing an old
127 # target_files zip.
128
129 if "mkyaffs2_extra_flags" not in d:
130 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700131 d["mkyaffs2_extra_flags"] = read_helper(
132 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700133 except KeyError:
134 # ok if flags don't exist
135 pass
136
137 if "recovery_api_version" not in d:
138 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700139 d["recovery_api_version"] = read_helper(
140 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 raise ValueError("can't find recovery API version in input target-files")
143
144 if "tool_extensions" not in d:
145 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800146 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700147 except KeyError:
148 # ok if extensions don't exist
149 pass
150
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800151 if "fstab_version" not in d:
152 d["fstab_version"] = "1"
153
Tao Bao84e75682015-07-19 02:38:53 -0700154 # A few properties are stored as links to the files in the out/ directory.
155 # It works fine with the build system. However, they are no longer available
156 # when (re)generating from target_files zip. If input_dir is not None, we
157 # are doing repacking. Redirect those properties to the actual files in the
158 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700159 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400160 # We carry a copy of file_contexts.bin under META/. If not available,
161 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700162 # to build images than the one running on device, such as when enabling
163 # system_root_image. In that case, we must have the one for image
164 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700165 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
166 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700167 if d.get("system_root_image") == "true":
168 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700169 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700170 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700171 if not os.path.exists(fc_config):
172 fc_config = None
173
174 if fc_config:
175 d["selinux_fc"] = fc_config
176
Tao Bao84e75682015-07-19 02:38:53 -0700177 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
178 if d.get("system_root_image") == "true":
179 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
180 d["ramdisk_fs_config"] = os.path.join(
181 input_dir, "META", "root_filesystem_config.txt")
182
Doug Zongker37974732010-09-16 17:44:38 -0700183 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800184 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700185 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700186 if not line:
187 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700188 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700189 if not value:
190 continue
Doug Zongker37974732010-09-16 17:44:38 -0700191 if name == "blocksize":
192 d[name] = value
193 else:
194 d[name + "_size"] = value
195 except KeyError:
196 pass
197
198 def makeint(key):
199 if key in d:
200 d[key] = int(d[key], 0)
201
202 makeint("recovery_api_version")
203 makeint("blocksize")
204 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700205 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700206 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700207 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700208 makeint("recovery_size")
209 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800210 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700211
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700212 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
213 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700215 return d
216
Doug Zongkerc9253822014-02-04 12:17:58 -0800217def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700218 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800219 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700220 except KeyError:
221 print "Warning: could not find SYSTEM/build.prop in %s" % zip
222 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700223 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700224
Michael Runge6e836112014-04-15 17:40:21 -0700225def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700226 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700227 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700228 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700229 if not line or line.startswith("#"):
230 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700231 if "=" in line:
232 name, value = line.split("=", 1)
233 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700234 return d
235
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700236def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700237 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700238 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700239 self.mount_point = mount_point
240 self.fs_type = fs_type
241 self.device = device
242 self.length = length
243 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700244 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700245
246 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800247 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700248 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800249 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700250 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700251
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252 if fstab_version == 1:
253 d = {}
254 for line in data.split("\n"):
255 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 if not line or line.startswith("#"):
257 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700259 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800260 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 options = None
262 if len(pieces) >= 4:
263 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800265 if len(pieces) >= 5:
266 options = pieces[4]
267 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700268 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800270 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700272
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 mount_point = pieces[0]
274 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 if options:
276 options = options.split(",")
277 for i in options:
278 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800280 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800282
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
284 device=pieces[2], length=length,
285 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800286
287 elif fstab_version == 2:
288 d = {}
289 for line in data.split("\n"):
290 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700291 if not line or line.startswith("#"):
292 continue
Tao Bao548eb762015-06-10 12:32:41 -0700293 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800294 pieces = line.split()
295 if len(pieces) != 5:
296 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
297
298 # Ignore entries that are managed by vold
299 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700300 if "voldmanaged=" in options:
301 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800302
303 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700304 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800305 options = options.split(",")
306 for i in options:
307 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700308 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800309 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800310 # Ignore all unknown options in the unified fstab
311 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800312
Tao Bao548eb762015-06-10 12:32:41 -0700313 mount_flags = pieces[3]
314 # Honor the SELinux context if present.
315 context = None
316 for i in mount_flags.split(","):
317 if i.startswith("context="):
318 context = i
319
Dan Albert8b72aef2015-03-23 19:13:21 -0700320 mount_point = pieces[1]
321 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700322 device=pieces[0], length=length,
323 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800324
325 else:
326 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
327
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700328 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700329 # system. Other areas assume system is always at "/system" so point /system
330 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700331 if system_root_image:
332 assert not d.has_key("/system") and d.has_key("/")
333 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700334 return d
335
336
Doug Zongker37974732010-09-16 17:44:38 -0700337def DumpInfoDict(d):
338 for k, v in sorted(d.items()):
339 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700340
Dan Albert8b72aef2015-03-23 19:13:21 -0700341
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700342def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
343 has_ramdisk=False):
344 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700345
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700346 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
347 'sourcedir'), and turn them into a boot image. Return the image data, or
348 None if sourcedir does not appear to contains files for building the
349 requested image."""
350
351 def make_ramdisk():
352 ramdisk_img = tempfile.NamedTemporaryFile()
353
354 if os.access(fs_config_file, os.F_OK):
355 cmd = ["mkbootfs", "-f", fs_config_file,
356 os.path.join(sourcedir, "RAMDISK")]
357 else:
358 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
359 p1 = Run(cmd, stdout=subprocess.PIPE)
360 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
361
362 p2.wait()
363 p1.wait()
364 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
365 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
366
367 return ramdisk_img
368
369 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
370 return None
371
372 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700373 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700374
Doug Zongkerd5131602012-08-02 14:46:42 -0700375 if info_dict is None:
376 info_dict = OPTIONS.info_dict
377
Doug Zongkereef39442009-04-02 12:14:19 -0700378 img = tempfile.NamedTemporaryFile()
379
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700380 if has_ramdisk:
381 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700382
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800383 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
384 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
385
386 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700387
Benoit Fradina45a8682014-07-14 21:00:43 +0200388 fn = os.path.join(sourcedir, "second")
389 if os.access(fn, os.F_OK):
390 cmd.append("--second")
391 cmd.append(fn)
392
Doug Zongker171f1cd2009-06-15 22:36:37 -0700393 fn = os.path.join(sourcedir, "cmdline")
394 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700395 cmd.append("--cmdline")
396 cmd.append(open(fn).read().rstrip("\n"))
397
398 fn = os.path.join(sourcedir, "base")
399 if os.access(fn, os.F_OK):
400 cmd.append("--base")
401 cmd.append(open(fn).read().rstrip("\n"))
402
Ying Wang4de6b5b2010-08-25 14:29:34 -0700403 fn = os.path.join(sourcedir, "pagesize")
404 if os.access(fn, os.F_OK):
405 cmd.append("--pagesize")
406 cmd.append(open(fn).read().rstrip("\n"))
407
Doug Zongkerd5131602012-08-02 14:46:42 -0700408 args = info_dict.get("mkbootimg_args", None)
409 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700410 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700411
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700412 if has_ramdisk:
413 cmd.extend(["--ramdisk", ramdisk_img.name])
414
Tao Baod95e9fd2015-03-29 23:07:41 -0700415 img_unsigned = None
416 if info_dict.get("vboot", None):
417 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700418 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700419 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700420 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700421
422 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700423 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700424 assert p.returncode == 0, "mkbootimg of %s image failed" % (
425 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700426
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100427 if (info_dict.get("boot_signer", None) == "true" and
428 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700429 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700430 cmd = [OPTIONS.boot_signer_path]
431 cmd.extend(OPTIONS.boot_signer_args)
432 cmd.extend([path, img.name,
433 info_dict["verity_key"] + ".pk8",
434 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700435 p = Run(cmd, stdout=subprocess.PIPE)
436 p.communicate()
437 assert p.returncode == 0, "boot_signer of %s image failed" % path
438
Tao Baod95e9fd2015-03-29 23:07:41 -0700439 # Sign the image if vboot is non-empty.
440 elif info_dict.get("vboot", None):
441 path = "/" + os.path.basename(sourcedir).lower()
442 img_keyblock = tempfile.NamedTemporaryFile()
443 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
444 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
445 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
446 img.name]
447 p = Run(cmd, stdout=subprocess.PIPE)
448 p.communicate()
449 assert p.returncode == 0, "vboot_signer of %s image failed" % path
450
Tao Baof3282b42015-04-01 11:21:55 -0700451 # Clean up the temp files.
452 img_unsigned.close()
453 img_keyblock.close()
454
Doug Zongkereef39442009-04-02 12:14:19 -0700455 img.seek(os.SEEK_SET, 0)
456 data = img.read()
457
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700458 if has_ramdisk:
459 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700460 img.close()
461
462 return data
463
464
Doug Zongkerd5131602012-08-02 14:46:42 -0700465def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
466 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700467 """Return a File object with the desired bootable image.
468
469 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
470 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
471 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700472
Doug Zongker55d93282011-01-25 17:03:34 -0800473 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
474 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700475 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800476 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700477
478 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
479 if os.path.exists(prebuilt_path):
480 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
481 return File.FromLocalFile(name, prebuilt_path)
482
483 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700484
485 if info_dict is None:
486 info_dict = OPTIONS.info_dict
487
488 # With system_root_image == "true", we don't pack ramdisk into the boot image.
489 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
490 prebuilt_name != "boot.img")
491
Doug Zongker6f1d0312014-08-22 08:07:12 -0700492 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700493 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
494 os.path.join(unpack_dir, fs_config),
495 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700496 if data:
497 return File(name, data)
498 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800499
Doug Zongkereef39442009-04-02 12:14:19 -0700500
Doug Zongker75f17362009-12-08 13:46:44 -0800501def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800502 """Unzip the given archive into a temporary directory and return the name.
503
504 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
505 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
506
507 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
508 main file), open for reading.
509 """
Doug Zongkereef39442009-04-02 12:14:19 -0700510
511 tmp = tempfile.mkdtemp(prefix="targetfiles-")
512 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800513
514 def unzip_to_dir(filename, dirname):
515 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
516 if pattern is not None:
517 cmd.append(pattern)
518 p = Run(cmd, stdout=subprocess.PIPE)
519 p.communicate()
520 if p.returncode != 0:
521 raise ExternalError("failed to unzip input target-files \"%s\"" %
522 (filename,))
523
524 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
525 if m:
526 unzip_to_dir(m.group(1), tmp)
527 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
528 filename = m.group(1)
529 else:
530 unzip_to_dir(filename, tmp)
531
532 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700533
534
535def GetKeyPasswords(keylist):
536 """Given a list of keys, prompt the user to enter passwords for
537 those which require them. Return a {key: password} dict. password
538 will be None if the key has no password."""
539
Doug Zongker8ce7c252009-05-22 13:34:54 -0700540 no_passwords = []
541 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700542 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700543 devnull = open("/dev/null", "w+b")
544 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800545 # We don't need a password for things that aren't really keys.
546 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700547 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700548 continue
549
T.R. Fullhart37e10522013-03-18 10:31:26 -0700550 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700551 "-inform", "DER", "-nocrypt"],
552 stdin=devnull.fileno(),
553 stdout=devnull.fileno(),
554 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700555 p.communicate()
556 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700557 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700558 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700559 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700560 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
561 "-inform", "DER", "-passin", "pass:"],
562 stdin=devnull.fileno(),
563 stdout=devnull.fileno(),
564 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700565 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700566 if p.returncode == 0:
567 # Encrypted key with empty string as password.
568 key_passwords[k] = ''
569 elif stderr.startswith('Error decrypting key'):
570 # Definitely encrypted key.
571 # It would have said "Error reading key" if it didn't parse correctly.
572 need_passwords.append(k)
573 else:
574 # Potentially, a type of key that openssl doesn't understand.
575 # We'll let the routines in signapk.jar handle it.
576 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700577 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700578
T.R. Fullhart37e10522013-03-18 10:31:26 -0700579 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700580 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700581 return key_passwords
582
583
Doug Zongker951495f2009-08-14 12:44:19 -0700584def SignFile(input_name, output_name, key, password, align=None,
585 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700586 """Sign the input_name zip/jar/apk, producing output_name. Use the
587 given key and password (the latter may be None if the key does not
588 have a password.
589
590 If align is an integer > 1, zipalign is run to align stored files in
591 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700592
593 If whole_file is true, use the "-w" option to SignApk to embed a
594 signature that covers the whole file in the archive comment of the
595 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700596 """
Doug Zongker951495f2009-08-14 12:44:19 -0700597
Doug Zongkereef39442009-04-02 12:14:19 -0700598 if align == 0 or align == 1:
599 align = None
600
601 if align:
602 temp = tempfile.NamedTemporaryFile()
603 sign_name = temp.name
604 else:
605 sign_name = output_name
606
Baligh Uddin339ee492014-09-05 11:18:07 -0700607 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700608 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
609 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700610 if whole_file:
611 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 cmd.extend([key + OPTIONS.public_key_suffix,
613 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700614 input_name, sign_name])
615
616 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700617 if password is not None:
618 password += "\n"
619 p.communicate(password)
620 if p.returncode != 0:
621 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
622
623 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700624 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700625 p.communicate()
626 if p.returncode != 0:
627 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
628 temp.close()
629
630
Doug Zongker37974732010-09-16 17:44:38 -0700631def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700632 """Check the data string passed against the max size limit, if
633 any, for the given target. Raise exception if the data is too big.
634 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700635
Dan Albert8b72aef2015-03-23 19:13:21 -0700636 if target.endswith(".img"):
637 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700638 mount_point = "/" + target
639
Ying Wangf8824af2014-06-03 14:07:27 -0700640 fs_type = None
641 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700642 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700643 if mount_point == "/userdata":
644 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700645 p = info_dict["fstab"][mount_point]
646 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800647 device = p.device
648 if "/" in device:
649 device = device[device.rfind("/")+1:]
650 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700651 if not fs_type or not limit:
652 return
Doug Zongkereef39442009-04-02 12:14:19 -0700653
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700654 if fs_type == "yaffs2":
655 # image size should be increased by 1/64th to account for the
656 # spare area (64 bytes per 2k page)
657 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800658 size = len(data)
659 pct = float(size) * 100.0 / limit
660 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
661 if pct >= 99.0:
662 raise ExternalError(msg)
663 elif pct >= 95.0:
664 print
665 print " WARNING: ", msg
666 print
667 elif OPTIONS.verbose:
668 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700669
670
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800671def ReadApkCerts(tf_zip):
672 """Given a target_files ZipFile, parse the META/apkcerts.txt file
673 and return a {package: cert} dict."""
674 certmap = {}
675 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
676 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700677 if not line:
678 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800679 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
680 r'private_key="(.*)"$', line)
681 if m:
682 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700683 public_key_suffix_len = len(OPTIONS.public_key_suffix)
684 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800685 if cert in SPECIAL_CERT_STRINGS and not privkey:
686 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700687 elif (cert.endswith(OPTIONS.public_key_suffix) and
688 privkey.endswith(OPTIONS.private_key_suffix) and
689 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
690 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800691 else:
692 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
693 return certmap
694
695
Doug Zongkereef39442009-04-02 12:14:19 -0700696COMMON_DOCSTRING = """
697 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700698 Prepend <dir>/bin to the list of places to search for binaries
699 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700700
Doug Zongker05d3dea2009-06-22 11:32:31 -0700701 -s (--device_specific) <file>
702 Path to the python module containing device-specific
703 releasetools code.
704
Doug Zongker8bec09e2009-11-30 15:37:14 -0800705 -x (--extra) <key=value>
706 Add a key/value pair to the 'extras' dict, which device-specific
707 extension code may look at.
708
Doug Zongkereef39442009-04-02 12:14:19 -0700709 -v (--verbose)
710 Show command lines being executed.
711
712 -h (--help)
713 Display this usage message and exit.
714"""
715
716def Usage(docstring):
717 print docstring.rstrip("\n")
718 print COMMON_DOCSTRING
719
720
721def ParseOptions(argv,
722 docstring,
723 extra_opts="", extra_long_opts=(),
724 extra_option_handler=None):
725 """Parse the options in argv and return any arguments that aren't
726 flags. docstring is the calling module's docstring, to be displayed
727 for errors and -h. extra_opts and extra_long_opts are for flags
728 defined by the caller, which are processed by passing them to
729 extra_option_handler."""
730
731 try:
732 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800733 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700734 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700735 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700736 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
737 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800738 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700739 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700740 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700741 Usage(docstring)
742 print "**", str(err), "**"
743 sys.exit(2)
744
Doug Zongkereef39442009-04-02 12:14:19 -0700745 for o, a in opts:
746 if o in ("-h", "--help"):
747 Usage(docstring)
748 sys.exit()
749 elif o in ("-v", "--verbose"):
750 OPTIONS.verbose = True
751 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700752 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700753 elif o in ("--signapk_path",):
754 OPTIONS.signapk_path = a
755 elif o in ("--extra_signapk_args",):
756 OPTIONS.extra_signapk_args = shlex.split(a)
757 elif o in ("--java_path",):
758 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700759 elif o in ("--java_args",):
760 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700761 elif o in ("--public_key_suffix",):
762 OPTIONS.public_key_suffix = a
763 elif o in ("--private_key_suffix",):
764 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800765 elif o in ("--boot_signer_path",):
766 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700767 elif o in ("--boot_signer_args",):
768 OPTIONS.boot_signer_args = shlex.split(a)
769 elif o in ("--verity_signer_path",):
770 OPTIONS.verity_signer_path = a
771 elif o in ("--verity_signer_args",):
772 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700773 elif o in ("-s", "--device_specific"):
774 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800775 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800776 key, value = a.split("=", 1)
777 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700778 else:
779 if extra_option_handler is None or not extra_option_handler(o, a):
780 assert False, "unknown option \"%s\"" % (o,)
781
Doug Zongker85448772014-09-09 14:59:20 -0700782 if OPTIONS.search_path:
783 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
784 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700785
786 return args
787
788
Doug Zongkerfc44a512014-08-26 13:10:25 -0700789def MakeTempFile(prefix=None, suffix=None):
790 """Make a temp file and add it to the list of things to be deleted
791 when Cleanup() is called. Return the filename."""
792 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
793 os.close(fd)
794 OPTIONS.tempfiles.append(fn)
795 return fn
796
797
Doug Zongkereef39442009-04-02 12:14:19 -0700798def Cleanup():
799 for i in OPTIONS.tempfiles:
800 if os.path.isdir(i):
801 shutil.rmtree(i)
802 else:
803 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700804
805
806class PasswordManager(object):
807 def __init__(self):
808 self.editor = os.getenv("EDITOR", None)
809 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
810
811 def GetPasswords(self, items):
812 """Get passwords corresponding to each string in 'items',
813 returning a dict. (The dict may have keys in addition to the
814 values in 'items'.)
815
816 Uses the passwords in $ANDROID_PW_FILE if available, letting the
817 user edit that file to add more needed passwords. If no editor is
818 available, or $ANDROID_PW_FILE isn't define, prompts the user
819 interactively in the ordinary way.
820 """
821
822 current = self.ReadFile()
823
824 first = True
825 while True:
826 missing = []
827 for i in items:
828 if i not in current or not current[i]:
829 missing.append(i)
830 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700831 if not missing:
832 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833
834 for i in missing:
835 current[i] = ""
836
837 if not first:
838 print "key file %s still missing some passwords." % (self.pwfile,)
839 answer = raw_input("try to edit again? [y]> ").strip()
840 if answer and answer[0] not in 'yY':
841 raise RuntimeError("key passwords unavailable")
842 first = False
843
844 current = self.UpdateAndReadFile(current)
845
Dan Albert8b72aef2015-03-23 19:13:21 -0700846 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700847 """Prompt the user to enter a value (password) for each key in
848 'current' whose value is fales. Returns a new dict with all the
849 values.
850 """
851 result = {}
852 for k, v in sorted(current.iteritems()):
853 if v:
854 result[k] = v
855 else:
856 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700857 result[k] = getpass.getpass(
858 "Enter password for %s key> " % k).strip()
859 if result[k]:
860 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700861 return result
862
863 def UpdateAndReadFile(self, current):
864 if not self.editor or not self.pwfile:
865 return self.PromptResult(current)
866
867 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700868 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700869 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
870 f.write("# (Additional spaces are harmless.)\n\n")
871
872 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700873 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
874 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700875 f.write("[[[ %s ]]] %s\n" % (v, k))
876 if not v and first_line is None:
877 # position cursor on first line with no password.
878 first_line = i + 4
879 f.close()
880
881 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
882 _, _ = p.communicate()
883
884 return self.ReadFile()
885
886 def ReadFile(self):
887 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700888 if self.pwfile is None:
889 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700890 try:
891 f = open(self.pwfile, "r")
892 for line in f:
893 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700894 if not line or line[0] == '#':
895 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700896 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
897 if not m:
898 print "failed to parse password file: ", line
899 else:
900 result[m.group(2)] = m.group(1)
901 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700902 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700903 if e.errno != errno.ENOENT:
904 print "error reading password file: ", str(e)
905 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700906
907
Dan Albert8e0178d2015-01-27 15:53:15 -0800908def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
909 compress_type=None):
910 import datetime
911
912 # http://b/18015246
913 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
914 # for files larger than 2GiB. We can work around this by adjusting their
915 # limit. Note that `zipfile.writestr()` will not work for strings larger than
916 # 2GiB. The Python interpreter sometimes rejects strings that large (though
917 # it isn't clear to me exactly what circumstances cause this).
918 # `zipfile.write()` must be used directly to work around this.
919 #
920 # This mess can be avoided if we port to python3.
921 saved_zip64_limit = zipfile.ZIP64_LIMIT
922 zipfile.ZIP64_LIMIT = (1 << 32) - 1
923
924 if compress_type is None:
925 compress_type = zip_file.compression
926 if arcname is None:
927 arcname = filename
928
929 saved_stat = os.stat(filename)
930
931 try:
932 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
933 # file to be zipped and reset it when we're done.
934 os.chmod(filename, perms)
935
936 # Use a fixed timestamp so the output is repeatable.
937 epoch = datetime.datetime.fromtimestamp(0)
938 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
939 os.utime(filename, (timestamp, timestamp))
940
941 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
942 finally:
943 os.chmod(filename, saved_stat.st_mode)
944 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
945 zipfile.ZIP64_LIMIT = saved_zip64_limit
946
947
Tao Bao58c1b962015-05-20 09:32:18 -0700948def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700949 compress_type=None):
950 """Wrap zipfile.writestr() function to work around the zip64 limit.
951
952 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
953 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
954 when calling crc32(bytes).
955
956 But it still works fine to write a shorter string into a large zip file.
957 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
958 when we know the string won't be too long.
959 """
960
961 saved_zip64_limit = zipfile.ZIP64_LIMIT
962 zipfile.ZIP64_LIMIT = (1 << 32) - 1
963
964 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
965 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700966 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700967 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700968 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800969 else:
Tao Baof3282b42015-04-01 11:21:55 -0700970 zinfo = zinfo_or_arcname
971
972 # If compress_type is given, it overrides the value in zinfo.
973 if compress_type is not None:
974 zinfo.compress_type = compress_type
975
Tao Bao58c1b962015-05-20 09:32:18 -0700976 # If perms is given, it has a priority.
977 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700978 # If perms doesn't set the file type, mark it as a regular file.
979 if perms & 0o770000 == 0:
980 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -0700981 zinfo.external_attr = perms << 16
982
Tao Baof3282b42015-04-01 11:21:55 -0700983 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700984 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
985
Dan Albert8b72aef2015-03-23 19:13:21 -0700986 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700987 zipfile.ZIP64_LIMIT = saved_zip64_limit
988
989
990def ZipClose(zip_file):
991 # http://b/18015246
992 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
993 # central directory.
994 saved_zip64_limit = zipfile.ZIP64_LIMIT
995 zipfile.ZIP64_LIMIT = (1 << 32) - 1
996
997 zip_file.close()
998
999 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001000
1001
1002class DeviceSpecificParams(object):
1003 module = None
1004 def __init__(self, **kwargs):
1005 """Keyword arguments to the constructor become attributes of this
1006 object, which is passed to all functions in the device-specific
1007 module."""
1008 for k, v in kwargs.iteritems():
1009 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001010 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001011
1012 if self.module is None:
1013 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001014 if not path:
1015 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001016 try:
1017 if os.path.isdir(path):
1018 info = imp.find_module("releasetools", [path])
1019 else:
1020 d, f = os.path.split(path)
1021 b, x = os.path.splitext(f)
1022 if x == ".py":
1023 f = b
1024 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001025 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001026 self.module = imp.load_module("device_specific", *info)
1027 except ImportError:
1028 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001029
1030 def _DoCall(self, function_name, *args, **kwargs):
1031 """Call the named function in the device-specific module, passing
1032 the given args and kwargs. The first argument to the call will be
1033 the DeviceSpecific object itself. If there is no module, or the
1034 module does not define the function, return the value of the
1035 'default' kwarg (which itself defaults to None)."""
1036 if self.module is None or not hasattr(self.module, function_name):
1037 return kwargs.get("default", None)
1038 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1039
1040 def FullOTA_Assertions(self):
1041 """Called after emitting the block of assertions at the top of a
1042 full OTA package. Implementations can add whatever additional
1043 assertions they like."""
1044 return self._DoCall("FullOTA_Assertions")
1045
Doug Zongkere5ff5902012-01-17 10:55:37 -08001046 def FullOTA_InstallBegin(self):
1047 """Called at the start of full OTA installation."""
1048 return self._DoCall("FullOTA_InstallBegin")
1049
Doug Zongker05d3dea2009-06-22 11:32:31 -07001050 def FullOTA_InstallEnd(self):
1051 """Called at the end of full OTA installation; typically this is
1052 used to install the image for the device's baseband processor."""
1053 return self._DoCall("FullOTA_InstallEnd")
1054
1055 def IncrementalOTA_Assertions(self):
1056 """Called after emitting the block of assertions at the top of an
1057 incremental OTA package. Implementations can add whatever
1058 additional assertions they like."""
1059 return self._DoCall("IncrementalOTA_Assertions")
1060
Doug Zongkere5ff5902012-01-17 10:55:37 -08001061 def IncrementalOTA_VerifyBegin(self):
1062 """Called at the start of the verification phase of incremental
1063 OTA installation; additional checks can be placed here to abort
1064 the script before any changes are made."""
1065 return self._DoCall("IncrementalOTA_VerifyBegin")
1066
Doug Zongker05d3dea2009-06-22 11:32:31 -07001067 def IncrementalOTA_VerifyEnd(self):
1068 """Called at the end of the verification phase of incremental OTA
1069 installation; additional checks can be placed here to abort the
1070 script before any changes are made."""
1071 return self._DoCall("IncrementalOTA_VerifyEnd")
1072
Doug Zongkere5ff5902012-01-17 10:55:37 -08001073 def IncrementalOTA_InstallBegin(self):
1074 """Called at the start of incremental OTA installation (after
1075 verification is complete)."""
1076 return self._DoCall("IncrementalOTA_InstallBegin")
1077
Doug Zongker05d3dea2009-06-22 11:32:31 -07001078 def IncrementalOTA_InstallEnd(self):
1079 """Called at the end of incremental OTA installation; typically
1080 this is used to install the image for the device's baseband
1081 processor."""
1082 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001083
1084class File(object):
1085 def __init__(self, name, data):
1086 self.name = name
1087 self.data = data
1088 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001089 self.sha1 = sha1(data).hexdigest()
1090
1091 @classmethod
1092 def FromLocalFile(cls, name, diskname):
1093 f = open(diskname, "rb")
1094 data = f.read()
1095 f.close()
1096 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001097
1098 def WriteToTemp(self):
1099 t = tempfile.NamedTemporaryFile()
1100 t.write(self.data)
1101 t.flush()
1102 return t
1103
Geremy Condra36bd3652014-02-06 19:45:10 -08001104 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001105 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001106
1107DIFF_PROGRAM_BY_EXT = {
1108 ".gz" : "imgdiff",
1109 ".zip" : ["imgdiff", "-z"],
1110 ".jar" : ["imgdiff", "-z"],
1111 ".apk" : ["imgdiff", "-z"],
1112 ".img" : "imgdiff",
1113 }
1114
1115class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001116 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001117 self.tf = tf
1118 self.sf = sf
1119 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001120 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001121
1122 def ComputePatch(self):
1123 """Compute the patch (as a string of data) needed to turn sf into
1124 tf. Returns the same tuple as GetPatch()."""
1125
1126 tf = self.tf
1127 sf = self.sf
1128
Doug Zongker24cd2802012-08-14 16:36:15 -07001129 if self.diff_program:
1130 diff_program = self.diff_program
1131 else:
1132 ext = os.path.splitext(tf.name)[1]
1133 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001134
1135 ttemp = tf.WriteToTemp()
1136 stemp = sf.WriteToTemp()
1137
1138 ext = os.path.splitext(tf.name)[1]
1139
1140 try:
1141 ptemp = tempfile.NamedTemporaryFile()
1142 if isinstance(diff_program, list):
1143 cmd = copy.copy(diff_program)
1144 else:
1145 cmd = [diff_program]
1146 cmd.append(stemp.name)
1147 cmd.append(ttemp.name)
1148 cmd.append(ptemp.name)
1149 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001150 err = []
1151 def run():
1152 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001153 if e:
1154 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001155 th = threading.Thread(target=run)
1156 th.start()
1157 th.join(timeout=300) # 5 mins
1158 if th.is_alive():
1159 print "WARNING: diff command timed out"
1160 p.terminate()
1161 th.join(5)
1162 if th.is_alive():
1163 p.kill()
1164 th.join()
1165
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001166 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001167 print "WARNING: failure running %s:\n%s\n" % (
1168 diff_program, "".join(err))
1169 self.patch = None
1170 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001171 diff = ptemp.read()
1172 finally:
1173 ptemp.close()
1174 stemp.close()
1175 ttemp.close()
1176
1177 self.patch = diff
1178 return self.tf, self.sf, self.patch
1179
1180
1181 def GetPatch(self):
1182 """Return a tuple (target_file, source_file, patch_data).
1183 patch_data may be None if ComputePatch hasn't been called, or if
1184 computing the patch failed."""
1185 return self.tf, self.sf, self.patch
1186
1187
1188def ComputeDifferences(diffs):
1189 """Call ComputePatch on all the Difference objects in 'diffs'."""
1190 print len(diffs), "diffs to compute"
1191
1192 # Do the largest files first, to try and reduce the long-pole effect.
1193 by_size = [(i.tf.size, i) for i in diffs]
1194 by_size.sort(reverse=True)
1195 by_size = [i[1] for i in by_size]
1196
1197 lock = threading.Lock()
1198 diff_iter = iter(by_size) # accessed under lock
1199
1200 def worker():
1201 try:
1202 lock.acquire()
1203 for d in diff_iter:
1204 lock.release()
1205 start = time.time()
1206 d.ComputePatch()
1207 dur = time.time() - start
1208 lock.acquire()
1209
1210 tf, sf, patch = d.GetPatch()
1211 if sf.name == tf.name:
1212 name = tf.name
1213 else:
1214 name = "%s (%s)" % (tf.name, sf.name)
1215 if patch is None:
1216 print "patching failed! %s" % (name,)
1217 else:
1218 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1219 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1220 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001221 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001222 print e
1223 raise
1224
1225 # start worker threads; wait for them all to finish.
1226 threads = [threading.Thread(target=worker)
1227 for i in range(OPTIONS.worker_threads)]
1228 for th in threads:
1229 th.start()
1230 while threads:
1231 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001232
1233
Dan Albert8b72aef2015-03-23 19:13:21 -07001234class BlockDifference(object):
1235 def __init__(self, partition, tgt, src=None, check_first_block=False,
1236 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001237 self.tgt = tgt
1238 self.src = src
1239 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001240 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001241
Tao Bao5ece99d2015-05-12 11:42:31 -07001242 # Due to http://b/20939131, check_first_block is disabled temporarily.
1243 assert not self.check_first_block
1244
Tao Baodd2a5892015-03-12 12:32:37 -07001245 if version is None:
1246 version = 1
1247 if OPTIONS.info_dict:
1248 version = max(
1249 int(i) for i in
1250 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1251 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001252
1253 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001254 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001255 tmpdir = tempfile.mkdtemp()
1256 OPTIONS.tempfiles.append(tmpdir)
1257 self.path = os.path.join(tmpdir, partition)
1258 b.Compute(self.path)
1259
1260 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1261
1262 def WriteScript(self, script, output_zip, progress=None):
1263 if not self.src:
1264 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001265 script.Print("Patching %s image unconditionally..." % (self.partition,))
1266 else:
1267 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001268
Dan Albert8b72aef2015-03-23 19:13:21 -07001269 if progress:
1270 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001271 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001272 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001273
1274 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001275 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001276 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001277 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001278 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001279 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1280 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001281 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001282 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1283 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001284 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001285 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001286 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001287 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001288 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001289 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001290 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001291 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001292 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001293
Tao Baodd2a5892015-03-12 12:32:37 -07001294 # When generating incrementals for the system and vendor partitions,
1295 # explicitly check the first block (which contains the superblock) of
1296 # the partition to see if it's what we expect. If this check fails,
1297 # give an explicit log message about the partition having been
1298 # remounted R/W (the most likely explanation) and the need to flash to
1299 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001300 if self.check_first_block:
1301 self._CheckFirstBlock(script)
1302
Tao Baodd2a5892015-03-12 12:32:37 -07001303 # Abort the OTA update. Note that the incremental OTA cannot be applied
1304 # even if it may match the checksum of the target partition.
1305 # a) If version < 3, operations like move and erase will make changes
1306 # unconditionally and damage the partition.
1307 # b) If version >= 3, it won't even reach here.
1308 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1309 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001310
Tao Bao5fcaaef2015-06-01 13:40:49 -07001311 def _WritePostInstallVerifyScript(self, script):
1312 partition = self.partition
1313 script.Print('Verifying the updated %s image...' % (partition,))
1314 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1315 ranges = self.tgt.care_map
1316 ranges_str = ranges.to_string_raw()
1317 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1318 self.device, ranges_str,
1319 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001320
1321 # Bug: 20881595
1322 # Verify that extended blocks are really zeroed out.
1323 if self.tgt.extended:
1324 ranges_str = self.tgt.extended.to_string_raw()
1325 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1326 self.device, ranges_str,
1327 self._HashZeroBlocks(self.tgt.extended.size())))
1328 script.Print('Verified the updated %s image.' % (partition,))
1329 script.AppendExtra(
1330 'else\n'
1331 ' abort("%s partition has unexpected non-zero contents after OTA '
1332 'update");\n'
1333 'endif;' % (partition,))
1334 else:
1335 script.Print('Verified the updated %s image.' % (partition,))
1336
Tao Bao5fcaaef2015-06-01 13:40:49 -07001337 script.AppendExtra(
1338 'else\n'
1339 ' abort("%s partition has unexpected contents after OTA update");\n'
1340 'endif;' % (partition,))
1341
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001342 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001343 ZipWrite(output_zip,
1344 '{}.transfer.list'.format(self.path),
1345 '{}.transfer.list'.format(self.partition))
1346 ZipWrite(output_zip,
1347 '{}.new.dat'.format(self.path),
1348 '{}.new.dat'.format(self.partition))
1349 ZipWrite(output_zip,
1350 '{}.patch.dat'.format(self.path),
1351 '{}.patch.dat'.format(self.partition),
1352 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001353
Dan Albert8e0178d2015-01-27 15:53:15 -08001354 call = ('block_image_update("{device}", '
1355 'package_extract_file("{partition}.transfer.list"), '
1356 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1357 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001358 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001359
Dan Albert8b72aef2015-03-23 19:13:21 -07001360 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001361 data = source.ReadRangeSet(ranges)
1362 ctx = sha1()
1363
1364 for p in data:
1365 ctx.update(p)
1366
1367 return ctx.hexdigest()
1368
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001369 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1370 """Return the hash value for all zero blocks."""
1371 zero_block = '\x00' * 4096
1372 ctx = sha1()
1373 for _ in range(num_blocks):
1374 ctx.update(zero_block)
1375
1376 return ctx.hexdigest()
1377
Tao Bao5ece99d2015-05-12 11:42:31 -07001378 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1379 # remounting R/W. Will change the checking to a finer-grained way to
1380 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001381 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001382 r = rangelib.RangeSet((0, 1))
1383 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001384
1385 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1386 'abort("%s has been remounted R/W; '
1387 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001388 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001389 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001390
1391DataImage = blockimgdiff.DataImage
1392
1393
Doug Zongker96a57e72010-09-26 14:57:41 -07001394# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001395PARTITION_TYPES = {
1396 "yaffs2": "MTD",
1397 "mtd": "MTD",
1398 "ext4": "EMMC",
1399 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001400 "f2fs": "EMMC",
1401 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001402}
Doug Zongker96a57e72010-09-26 14:57:41 -07001403
1404def GetTypeAndDevice(mount_point, info):
1405 fstab = info["fstab"]
1406 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001407 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1408 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001409 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001410 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001411
1412
1413def ParseCertificate(data):
1414 """Parse a PEM-format certificate."""
1415 cert = []
1416 save = False
1417 for line in data.split("\n"):
1418 if "--END CERTIFICATE--" in line:
1419 break
1420 if save:
1421 cert.append(line)
1422 if "--BEGIN CERTIFICATE--" in line:
1423 save = True
1424 cert = "".join(cert).decode('base64')
1425 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001426
Doug Zongker412c02f2014-02-13 10:58:24 -08001427def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1428 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001429 """Generate a binary patch that creates the recovery image starting
1430 with the boot image. (Most of the space in these images is just the
1431 kernel, which is identical for the two, so the resulting patch
1432 should be efficient.) Add it to the output zip, along with a shell
1433 script that is run from init.rc on first boot to actually do the
1434 patching and install the new recovery image.
1435
1436 recovery_img and boot_img should be File objects for the
1437 corresponding images. info should be the dictionary returned by
1438 common.LoadInfoDict() on the input target_files.
1439 """
1440
Doug Zongker412c02f2014-02-13 10:58:24 -08001441 if info_dict is None:
1442 info_dict = OPTIONS.info_dict
1443
Tao Baof2cffbd2015-07-22 12:33:18 -07001444 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001445 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001446
Tao Baof2cffbd2015-07-22 12:33:18 -07001447 if full_recovery_image:
1448 output_sink("etc/recovery.img", recovery_img.data)
1449
1450 else:
1451 diff_program = ["imgdiff"]
1452 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1453 if os.path.exists(path):
1454 diff_program.append("-b")
1455 diff_program.append(path)
1456 bonus_args = "-b /system/etc/recovery-resource.dat"
1457 else:
1458 bonus_args = ""
1459
1460 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1461 _, _, patch = d.ComputePatch()
1462 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001463
Dan Albertebb19aa2015-03-27 19:11:53 -07001464 try:
1465 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1466 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1467 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001468 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001469
Tao Baof2cffbd2015-07-22 12:33:18 -07001470 if full_recovery_image:
1471 sh = """#!/system/bin/sh
1472if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1473 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"
1474else
1475 log -t recovery "Recovery image already installed"
1476fi
1477""" % {'type': recovery_type,
1478 'device': recovery_device,
1479 'sha1': recovery_img.sha1,
1480 'size': recovery_img.size}
1481 else:
1482 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001483if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1484 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"
1485else
1486 log -t recovery "Recovery image already installed"
1487fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001488""" % {'boot_size': boot_img.size,
1489 'boot_sha1': boot_img.sha1,
1490 'recovery_size': recovery_img.size,
1491 'recovery_sha1': recovery_img.sha1,
1492 'boot_type': boot_type,
1493 'boot_device': boot_device,
1494 'recovery_type': recovery_type,
1495 'recovery_device': recovery_device,
1496 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001497
1498 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001499 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001500 # target-files expects it to be, and put it there.
1501 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001502 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001503 if system_root_image:
1504 init_rc_dir = os.path.join(input_dir, "ROOT")
1505 else:
1506 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001507 init_rc_files = os.listdir(init_rc_dir)
1508 for init_rc_file in init_rc_files:
1509 if (not init_rc_file.startswith('init.') or
1510 not init_rc_file.endswith('.rc')):
1511 continue
1512
1513 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001514 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001515 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001516 if m:
1517 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001518 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001519 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001520
1521 if found:
1522 break
1523
1524 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001525
1526 output_sink(sh_location, sh)