blob: 051a22d0dfe35f710ed04664c86735054092187b [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Bao2ed665a2015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Baligh Uddin852a5b52014-11-20 09:52:05 -080052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.verbose = False
55 self.tempfiles = []
56 self.device_specific = None
57 self.extras = {}
58 self.info_dict = None
59 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070060 # Stash size cannot exceed cache_size * threshold.
61 self.cache_size = None
62 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070063
64
65OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070066
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080067
68# Values for "certificate" in apkcerts that mean special things.
69SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
70
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072class ExternalError(RuntimeError):
73 pass
Doug Zongkereef39442009-04-02 12:14:19 -070074
75
76def Run(args, **kwargs):
77 """Create and return a subprocess.Popen object, printing the command
78 line on the terminal if -v was specified."""
79 if OPTIONS.verbose:
80 print " running: ", " ".join(args)
81 return subprocess.Popen(args, **kwargs)
82
83
Ying Wang7e6d4e42010-12-13 16:25:36 -080084def CloseInheritedPipes():
85 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
86 before doing other work."""
87 if platform.system() != "Darwin":
88 return
89 for d in range(3, 1025):
90 try:
91 stat = os.fstat(d)
92 if stat is not None:
93 pipebit = stat[0] & 0x1000
94 if pipebit != 0:
95 os.close(d)
96 except OSError:
97 pass
98
99
Tao Bao2c15d9e2015-07-09 11:51:16 -0700100def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101 """Read and parse the META/misc_info.txt key/value pairs from the
102 input target files and return a dict."""
103
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 if isinstance(input_file, zipfile.ZipFile):
106 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 try:
110 with open(path) as f:
111 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 if e.errno == errno.ENOENT:
114 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700115 d = {}
116 try:
Michael Runge6e836112014-04-15 17:40:21 -0700117 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700118 except KeyError:
119 # ok if misc_info.txt doesn't exist
120 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121
Doug Zongker37974732010-09-16 17:44:38 -0700122 # backwards compatibility: These values used to be in their own
123 # files. Look for them, in case we're processing an old
124 # target_files zip.
125
126 if "mkyaffs2_extra_flags" not in d:
127 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700128 d["mkyaffs2_extra_flags"] = read_helper(
129 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700130 except KeyError:
131 # ok if flags don't exist
132 pass
133
134 if "recovery_api_version" not in d:
135 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700136 d["recovery_api_version"] = read_helper(
137 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 raise ValueError("can't find recovery API version in input target-files")
140
141 if "tool_extensions" not in d:
142 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 # ok if extensions don't exist
146 pass
147
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800148 if "fstab_version" not in d:
149 d["fstab_version"] = "1"
150
Tao Bao84e75682015-07-19 02:38:53 -0700151 # A few properties are stored as links to the files in the out/ directory.
152 # It works fine with the build system. However, they are no longer available
153 # when (re)generating from target_files zip. If input_dir is not None, we
154 # are doing repacking. Redirect those properties to the actual files in the
155 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700156 if input_dir is not None:
Tao Bao84e75682015-07-19 02:38:53 -0700157 # We carry a copy of file_contexts under META/. If not available, search
158 # BOOT/RAMDISK/. Note that sometimes we may need a different file_contexts
159 # to build images than the one running on device, such as when enabling
160 # system_root_image. In that case, we must have the one for image
161 # generation copied to META/.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700162 fc_config = os.path.join(input_dir, "META", "file_contexts")
Tao Bao84e75682015-07-19 02:38:53 -0700163 if d.get("system_root_image") == "true":
164 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700165 if not os.path.exists(fc_config):
166 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", "file_contexts")
167 if not os.path.exists(fc_config):
168 fc_config = None
169
170 if fc_config:
171 d["selinux_fc"] = fc_config
172
Tao Bao84e75682015-07-19 02:38:53 -0700173 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
174 if d.get("system_root_image") == "true":
175 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
176 d["ramdisk_fs_config"] = os.path.join(
177 input_dir, "META", "root_filesystem_config.txt")
178
Doug Zongker37974732010-09-16 17:44:38 -0700179 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700181 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700182 if not line:
183 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700184 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700185 if not value:
186 continue
Doug Zongker37974732010-09-16 17:44:38 -0700187 if name == "blocksize":
188 d[name] = value
189 else:
190 d[name + "_size"] = value
191 except KeyError:
192 pass
193
194 def makeint(key):
195 if key in d:
196 d[key] = int(d[key], 0)
197
198 makeint("recovery_api_version")
199 makeint("blocksize")
200 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700201 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700202 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700203 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700204 makeint("recovery_size")
205 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800206 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700207
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700208 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
209 d.get("system_root_image", False))
Doug Zongkerc9253822014-02-04 12:17:58 -0800210 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700211 return d
212
Doug Zongkerc9253822014-02-04 12:17:58 -0800213def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700214 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800215 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700216 except KeyError:
217 print "Warning: could not find SYSTEM/build.prop in %s" % zip
218 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700219 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700220
Michael Runge6e836112014-04-15 17:40:21 -0700221def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700222 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700223 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700224 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700225 if not line or line.startswith("#"):
226 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700227 if "=" in line:
228 name, value = line.split("=", 1)
229 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700230 return d
231
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700232def LoadRecoveryFSTab(read_helper, fstab_version, system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700233 class Partition(object):
Tao Baodf06e962015-06-10 12:32:41 -0700234 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 self.mount_point = mount_point
236 self.fs_type = fs_type
237 self.device = device
238 self.length = length
239 self.device2 = device2
Tao Baodf06e962015-06-10 12:32:41 -0700240 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700241
242 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800243 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700244 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800245 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700246 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700247
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800248 if fstab_version == 1:
249 d = {}
250 for line in data.split("\n"):
251 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 if not line or line.startswith("#"):
253 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800254 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700255 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 options = None
258 if len(pieces) >= 4:
259 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700260 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 if len(pieces) >= 5:
262 options = pieces[4]
263 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800265 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800266 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700268
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 mount_point = pieces[0]
270 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271 if options:
272 options = options.split(",")
273 for i in options:
274 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800276 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800278
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
280 device=pieces[2], length=length,
281 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800282
283 elif fstab_version == 2:
284 d = {}
285 for line in data.split("\n"):
286 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 if not line or line.startswith("#"):
288 continue
Tao Baodf06e962015-06-10 12:32:41 -0700289 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800290 pieces = line.split()
291 if len(pieces) != 5:
292 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
293
294 # Ignore entries that are managed by vold
295 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 if "voldmanaged=" in options:
297 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800298
299 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700300 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800301 options = options.split(",")
302 for i in options:
303 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700304 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800305 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800306 # Ignore all unknown options in the unified fstab
307 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800308
Tao Baodf06e962015-06-10 12:32:41 -0700309 mount_flags = pieces[3]
310 # Honor the SELinux context if present.
311 context = None
312 for i in mount_flags.split(","):
313 if i.startswith("context="):
314 context = i
315
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 mount_point = pieces[1]
317 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Baodf06e962015-06-10 12:32:41 -0700318 device=pieces[0], length=length,
319 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800320
321 else:
322 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
323
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700324 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700325 # system. Other areas assume system is always at "/system" so point /system
326 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700327 if system_root_image:
328 assert not d.has_key("/system") and d.has_key("/")
329 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700330 return d
331
332
Doug Zongker37974732010-09-16 17:44:38 -0700333def DumpInfoDict(d):
334 for k, v in sorted(d.items()):
335 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700336
Dan Albert8b72aef2015-03-23 19:13:21 -0700337
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700338def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
339 has_ramdisk=False):
340 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700341
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700342 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
343 'sourcedir'), and turn them into a boot image. Return the image data, or
344 None if sourcedir does not appear to contains files for building the
345 requested image."""
346
347 def make_ramdisk():
348 ramdisk_img = tempfile.NamedTemporaryFile()
349
350 if os.access(fs_config_file, os.F_OK):
351 cmd = ["mkbootfs", "-f", fs_config_file,
352 os.path.join(sourcedir, "RAMDISK")]
353 else:
354 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
355 p1 = Run(cmd, stdout=subprocess.PIPE)
356 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
357
358 p2.wait()
359 p1.wait()
360 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
361 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
362
363 return ramdisk_img
364
365 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
366 return None
367
368 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700369 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700370
Doug Zongkerd5131602012-08-02 14:46:42 -0700371 if info_dict is None:
372 info_dict = OPTIONS.info_dict
373
Doug Zongkereef39442009-04-02 12:14:19 -0700374 img = tempfile.NamedTemporaryFile()
375
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700376 if has_ramdisk:
377 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700378
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800379 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
380 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
381
382 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700383
Benoit Fradina45a8682014-07-14 21:00:43 +0200384 fn = os.path.join(sourcedir, "second")
385 if os.access(fn, os.F_OK):
386 cmd.append("--second")
387 cmd.append(fn)
388
Doug Zongker171f1cd2009-06-15 22:36:37 -0700389 fn = os.path.join(sourcedir, "cmdline")
390 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700391 cmd.append("--cmdline")
392 cmd.append(open(fn).read().rstrip("\n"))
393
394 fn = os.path.join(sourcedir, "base")
395 if os.access(fn, os.F_OK):
396 cmd.append("--base")
397 cmd.append(open(fn).read().rstrip("\n"))
398
Ying Wang4de6b5b2010-08-25 14:29:34 -0700399 fn = os.path.join(sourcedir, "pagesize")
400 if os.access(fn, os.F_OK):
401 cmd.append("--pagesize")
402 cmd.append(open(fn).read().rstrip("\n"))
403
Doug Zongkerd5131602012-08-02 14:46:42 -0700404 args = info_dict.get("mkbootimg_args", None)
405 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700406 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700407
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700408 if has_ramdisk:
409 cmd.extend(["--ramdisk", ramdisk_img.name])
410
Tao Baod95e9fd2015-03-29 23:07:41 -0700411 img_unsigned = None
412 if info_dict.get("vboot", None):
413 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700414 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700415 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700416 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700417
418 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700419 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700420 assert p.returncode == 0, "mkbootimg of %s image failed" % (
421 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700422
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700423 if info_dict.get("verity_key", None):
424 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800425 cmd = [OPTIONS.boot_signer_path, path, img.name,
426 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700427 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700428 p = Run(cmd, stdout=subprocess.PIPE)
429 p.communicate()
430 assert p.returncode == 0, "boot_signer of %s image failed" % path
431
Tao Baod95e9fd2015-03-29 23:07:41 -0700432 # Sign the image if vboot is non-empty.
433 elif info_dict.get("vboot", None):
434 path = "/" + os.path.basename(sourcedir).lower()
435 img_keyblock = tempfile.NamedTemporaryFile()
436 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
437 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
438 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
439 img.name]
440 p = Run(cmd, stdout=subprocess.PIPE)
441 p.communicate()
442 assert p.returncode == 0, "vboot_signer of %s image failed" % path
443
Tao Bao2ed665a2015-04-01 11:21:55 -0700444 # Clean up the temp files.
445 img_unsigned.close()
446 img_keyblock.close()
447
Doug Zongkereef39442009-04-02 12:14:19 -0700448 img.seek(os.SEEK_SET, 0)
449 data = img.read()
450
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700451 if has_ramdisk:
452 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700453 img.close()
454
455 return data
456
457
Doug Zongkerd5131602012-08-02 14:46:42 -0700458def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
459 info_dict=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700460 """Return a File object with the desired bootable image.
461
462 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
463 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
464 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700465
Doug Zongker55d93282011-01-25 17:03:34 -0800466 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
467 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700468 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800469 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700470
471 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
472 if os.path.exists(prebuilt_path):
473 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
474 return File.FromLocalFile(name, prebuilt_path)
475
476 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700477
478 if info_dict is None:
479 info_dict = OPTIONS.info_dict
480
481 # With system_root_image == "true", we don't pack ramdisk into the boot image.
482 has_ramdisk = (info_dict.get("system_root_image", None) != "true" or
483 prebuilt_name != "boot.img")
484
Doug Zongker6f1d0312014-08-22 08:07:12 -0700485 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700486 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
487 os.path.join(unpack_dir, fs_config),
488 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700489 if data:
490 return File(name, data)
491 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800492
Doug Zongkereef39442009-04-02 12:14:19 -0700493
Doug Zongker75f17362009-12-08 13:46:44 -0800494def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800495 """Unzip the given archive into a temporary directory and return the name.
496
497 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
498 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
499
500 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
501 main file), open for reading.
502 """
Doug Zongkereef39442009-04-02 12:14:19 -0700503
504 tmp = tempfile.mkdtemp(prefix="targetfiles-")
505 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800506
507 def unzip_to_dir(filename, dirname):
508 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
509 if pattern is not None:
510 cmd.append(pattern)
511 p = Run(cmd, stdout=subprocess.PIPE)
512 p.communicate()
513 if p.returncode != 0:
514 raise ExternalError("failed to unzip input target-files \"%s\"" %
515 (filename,))
516
517 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
518 if m:
519 unzip_to_dir(m.group(1), tmp)
520 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
521 filename = m.group(1)
522 else:
523 unzip_to_dir(filename, tmp)
524
525 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700526
527
528def GetKeyPasswords(keylist):
529 """Given a list of keys, prompt the user to enter passwords for
530 those which require them. Return a {key: password} dict. password
531 will be None if the key has no password."""
532
Doug Zongker8ce7c252009-05-22 13:34:54 -0700533 no_passwords = []
534 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700535 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700536 devnull = open("/dev/null", "w+b")
537 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800538 # We don't need a password for things that aren't really keys.
539 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700540 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700541 continue
542
T.R. Fullhart37e10522013-03-18 10:31:26 -0700543 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700544 "-inform", "DER", "-nocrypt"],
545 stdin=devnull.fileno(),
546 stdout=devnull.fileno(),
547 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700548 p.communicate()
549 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700550 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700551 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700552 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700553 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
554 "-inform", "DER", "-passin", "pass:"],
555 stdin=devnull.fileno(),
556 stdout=devnull.fileno(),
557 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700558 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700559 if p.returncode == 0:
560 # Encrypted key with empty string as password.
561 key_passwords[k] = ''
562 elif stderr.startswith('Error decrypting key'):
563 # Definitely encrypted key.
564 # It would have said "Error reading key" if it didn't parse correctly.
565 need_passwords.append(k)
566 else:
567 # Potentially, a type of key that openssl doesn't understand.
568 # We'll let the routines in signapk.jar handle it.
569 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700570 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700571
T.R. Fullhart37e10522013-03-18 10:31:26 -0700572 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700573 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700574 return key_passwords
575
576
Doug Zongker951495f2009-08-14 12:44:19 -0700577def SignFile(input_name, output_name, key, password, align=None,
578 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700579 """Sign the input_name zip/jar/apk, producing output_name. Use the
580 given key and password (the latter may be None if the key does not
581 have a password.
582
583 If align is an integer > 1, zipalign is run to align stored files in
584 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700585
586 If whole_file is true, use the "-w" option to SignApk to embed a
587 signature that covers the whole file in the archive comment of the
588 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700589 """
Doug Zongker951495f2009-08-14 12:44:19 -0700590
Doug Zongkereef39442009-04-02 12:14:19 -0700591 if align == 0 or align == 1:
592 align = None
593
594 if align:
595 temp = tempfile.NamedTemporaryFile()
596 sign_name = temp.name
597 else:
598 sign_name = output_name
599
Baligh Uddin339ee492014-09-05 11:18:07 -0700600 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700601 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
602 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700603 if whole_file:
604 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700605 cmd.extend([key + OPTIONS.public_key_suffix,
606 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700607 input_name, sign_name])
608
609 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700610 if password is not None:
611 password += "\n"
612 p.communicate(password)
613 if p.returncode != 0:
614 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
615
616 if align:
Brian Carlstrom663127d2015-05-22 15:51:19 -0700617 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700618 p.communicate()
619 if p.returncode != 0:
620 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
621 temp.close()
622
623
Doug Zongker37974732010-09-16 17:44:38 -0700624def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700625 """Check the data string passed against the max size limit, if
626 any, for the given target. Raise exception if the data is too big.
627 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700628
Dan Albert8b72aef2015-03-23 19:13:21 -0700629 if target.endswith(".img"):
630 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700631 mount_point = "/" + target
632
Ying Wangf8824af2014-06-03 14:07:27 -0700633 fs_type = None
634 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700635 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700636 if mount_point == "/userdata":
637 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700638 p = info_dict["fstab"][mount_point]
639 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800640 device = p.device
641 if "/" in device:
642 device = device[device.rfind("/")+1:]
643 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700644 if not fs_type or not limit:
645 return
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700647 if fs_type == "yaffs2":
648 # image size should be increased by 1/64th to account for the
649 # spare area (64 bytes per 2k page)
650 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800651 size = len(data)
652 pct = float(size) * 100.0 / limit
653 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
654 if pct >= 99.0:
655 raise ExternalError(msg)
656 elif pct >= 95.0:
657 print
658 print " WARNING: ", msg
659 print
660 elif OPTIONS.verbose:
661 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700662
663
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800664def ReadApkCerts(tf_zip):
665 """Given a target_files ZipFile, parse the META/apkcerts.txt file
666 and return a {package: cert} dict."""
667 certmap = {}
668 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
669 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700670 if not line:
671 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800672 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
673 r'private_key="(.*)"$', line)
674 if m:
675 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700676 public_key_suffix_len = len(OPTIONS.public_key_suffix)
677 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800678 if cert in SPECIAL_CERT_STRINGS and not privkey:
679 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700680 elif (cert.endswith(OPTIONS.public_key_suffix) and
681 privkey.endswith(OPTIONS.private_key_suffix) and
682 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
683 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800684 else:
685 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
686 return certmap
687
688
Doug Zongkereef39442009-04-02 12:14:19 -0700689COMMON_DOCSTRING = """
690 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700691 Prepend <dir>/bin to the list of places to search for binaries
692 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700693
Doug Zongker05d3dea2009-06-22 11:32:31 -0700694 -s (--device_specific) <file>
695 Path to the python module containing device-specific
696 releasetools code.
697
Doug Zongker8bec09e2009-11-30 15:37:14 -0800698 -x (--extra) <key=value>
699 Add a key/value pair to the 'extras' dict, which device-specific
700 extension code may look at.
701
Doug Zongkereef39442009-04-02 12:14:19 -0700702 -v (--verbose)
703 Show command lines being executed.
704
705 -h (--help)
706 Display this usage message and exit.
707"""
708
709def Usage(docstring):
710 print docstring.rstrip("\n")
711 print COMMON_DOCSTRING
712
713
714def ParseOptions(argv,
715 docstring,
716 extra_opts="", extra_long_opts=(),
717 extra_option_handler=None):
718 """Parse the options in argv and return any arguments that aren't
719 flags. docstring is the calling module's docstring, to be displayed
720 for errors and -h. extra_opts and extra_long_opts are for flags
721 defined by the caller, which are processed by passing them to
722 extra_option_handler."""
723
724 try:
725 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800726 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700727 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700728 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800729 "private_key_suffix=", "boot_signer_path=", "device_specific=",
730 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700731 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700732 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700733 Usage(docstring)
734 print "**", str(err), "**"
735 sys.exit(2)
736
Doug Zongkereef39442009-04-02 12:14:19 -0700737 for o, a in opts:
738 if o in ("-h", "--help"):
739 Usage(docstring)
740 sys.exit()
741 elif o in ("-v", "--verbose"):
742 OPTIONS.verbose = True
743 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700744 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700745 elif o in ("--signapk_path",):
746 OPTIONS.signapk_path = a
747 elif o in ("--extra_signapk_args",):
748 OPTIONS.extra_signapk_args = shlex.split(a)
749 elif o in ("--java_path",):
750 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700751 elif o in ("--java_args",):
752 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700753 elif o in ("--public_key_suffix",):
754 OPTIONS.public_key_suffix = a
755 elif o in ("--private_key_suffix",):
756 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800757 elif o in ("--boot_signer_path",):
758 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700759 elif o in ("-s", "--device_specific"):
760 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800761 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800762 key, value = a.split("=", 1)
763 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700764 else:
765 if extra_option_handler is None or not extra_option_handler(o, a):
766 assert False, "unknown option \"%s\"" % (o,)
767
Doug Zongker85448772014-09-09 14:59:20 -0700768 if OPTIONS.search_path:
769 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
770 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700771
772 return args
773
774
Doug Zongkerfc44a512014-08-26 13:10:25 -0700775def MakeTempFile(prefix=None, suffix=None):
776 """Make a temp file and add it to the list of things to be deleted
777 when Cleanup() is called. Return the filename."""
778 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
779 os.close(fd)
780 OPTIONS.tempfiles.append(fn)
781 return fn
782
783
Doug Zongkereef39442009-04-02 12:14:19 -0700784def Cleanup():
785 for i in OPTIONS.tempfiles:
786 if os.path.isdir(i):
787 shutil.rmtree(i)
788 else:
789 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700790
791
792class PasswordManager(object):
793 def __init__(self):
794 self.editor = os.getenv("EDITOR", None)
795 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
796
797 def GetPasswords(self, items):
798 """Get passwords corresponding to each string in 'items',
799 returning a dict. (The dict may have keys in addition to the
800 values in 'items'.)
801
802 Uses the passwords in $ANDROID_PW_FILE if available, letting the
803 user edit that file to add more needed passwords. If no editor is
804 available, or $ANDROID_PW_FILE isn't define, prompts the user
805 interactively in the ordinary way.
806 """
807
808 current = self.ReadFile()
809
810 first = True
811 while True:
812 missing = []
813 for i in items:
814 if i not in current or not current[i]:
815 missing.append(i)
816 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700817 if not missing:
818 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700819
820 for i in missing:
821 current[i] = ""
822
823 if not first:
824 print "key file %s still missing some passwords." % (self.pwfile,)
825 answer = raw_input("try to edit again? [y]> ").strip()
826 if answer and answer[0] not in 'yY':
827 raise RuntimeError("key passwords unavailable")
828 first = False
829
830 current = self.UpdateAndReadFile(current)
831
Dan Albert8b72aef2015-03-23 19:13:21 -0700832 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833 """Prompt the user to enter a value (password) for each key in
834 'current' whose value is fales. Returns a new dict with all the
835 values.
836 """
837 result = {}
838 for k, v in sorted(current.iteritems()):
839 if v:
840 result[k] = v
841 else:
842 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700843 result[k] = getpass.getpass(
844 "Enter password for %s key> " % k).strip()
845 if result[k]:
846 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700847 return result
848
849 def UpdateAndReadFile(self, current):
850 if not self.editor or not self.pwfile:
851 return self.PromptResult(current)
852
853 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700854 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700855 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
856 f.write("# (Additional spaces are harmless.)\n\n")
857
858 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700859 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
860 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700861 f.write("[[[ %s ]]] %s\n" % (v, k))
862 if not v and first_line is None:
863 # position cursor on first line with no password.
864 first_line = i + 4
865 f.close()
866
867 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
868 _, _ = p.communicate()
869
870 return self.ReadFile()
871
872 def ReadFile(self):
873 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700874 if self.pwfile is None:
875 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700876 try:
877 f = open(self.pwfile, "r")
878 for line in f:
879 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700880 if not line or line[0] == '#':
881 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700882 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
883 if not m:
884 print "failed to parse password file: ", line
885 else:
886 result[m.group(2)] = m.group(1)
887 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700888 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700889 if e.errno != errno.ENOENT:
890 print "error reading password file: ", str(e)
891 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700892
893
Dan Albert8e0178d2015-01-27 15:53:15 -0800894def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
895 compress_type=None):
896 import datetime
897
898 # http://b/18015246
899 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
900 # for files larger than 2GiB. We can work around this by adjusting their
901 # limit. Note that `zipfile.writestr()` will not work for strings larger than
902 # 2GiB. The Python interpreter sometimes rejects strings that large (though
903 # it isn't clear to me exactly what circumstances cause this).
904 # `zipfile.write()` must be used directly to work around this.
905 #
906 # This mess can be avoided if we port to python3.
907 saved_zip64_limit = zipfile.ZIP64_LIMIT
908 zipfile.ZIP64_LIMIT = (1 << 32) - 1
909
910 if compress_type is None:
911 compress_type = zip_file.compression
912 if arcname is None:
913 arcname = filename
914
915 saved_stat = os.stat(filename)
916
917 try:
918 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
919 # file to be zipped and reset it when we're done.
920 os.chmod(filename, perms)
921
922 # Use a fixed timestamp so the output is repeatable.
923 epoch = datetime.datetime.fromtimestamp(0)
924 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
925 os.utime(filename, (timestamp, timestamp))
926
927 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
928 finally:
929 os.chmod(filename, saved_stat.st_mode)
930 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
931 zipfile.ZIP64_LIMIT = saved_zip64_limit
932
933
Tao Bao97734652015-05-20 09:32:18 -0700934def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Bao2ed665a2015-04-01 11:21:55 -0700935 compress_type=None):
936 """Wrap zipfile.writestr() function to work around the zip64 limit.
937
938 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
939 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
940 when calling crc32(bytes).
941
942 But it still works fine to write a shorter string into a large zip file.
943 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
944 when we know the string won't be too long.
945 """
946
947 saved_zip64_limit = zipfile.ZIP64_LIMIT
948 zipfile.ZIP64_LIMIT = (1 << 32) - 1
949
950 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
951 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700952 zinfo.compress_type = zip_file.compression
Tao Bao97734652015-05-20 09:32:18 -0700953 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -0700954 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -0800955 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700956 zinfo = zinfo_or_arcname
957
958 # If compress_type is given, it overrides the value in zinfo.
959 if compress_type is not None:
960 zinfo.compress_type = compress_type
961
Tao Bao97734652015-05-20 09:32:18 -0700962 # If perms is given, it has a priority.
963 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -0700964 # If perms doesn't set the file type, mark it as a regular file.
965 if perms & 0o770000 == 0:
966 perms |= 0o100000
Tao Bao97734652015-05-20 09:32:18 -0700967 zinfo.external_attr = perms << 16
968
Tao Bao2ed665a2015-04-01 11:21:55 -0700969 # Use a fixed timestamp so the output is repeatable.
Tao Bao2ed665a2015-04-01 11:21:55 -0700970 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
971
Dan Albert8b72aef2015-03-23 19:13:21 -0700972 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700973 zipfile.ZIP64_LIMIT = saved_zip64_limit
974
975
976def ZipClose(zip_file):
977 # http://b/18015246
978 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
979 # central directory.
980 saved_zip64_limit = zipfile.ZIP64_LIMIT
981 zipfile.ZIP64_LIMIT = (1 << 32) - 1
982
983 zip_file.close()
984
985 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700986
987
988class DeviceSpecificParams(object):
989 module = None
990 def __init__(self, **kwargs):
991 """Keyword arguments to the constructor become attributes of this
992 object, which is passed to all functions in the device-specific
993 module."""
994 for k, v in kwargs.iteritems():
995 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800996 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700997
998 if self.module is None:
999 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001000 if not path:
1001 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001002 try:
1003 if os.path.isdir(path):
1004 info = imp.find_module("releasetools", [path])
1005 else:
1006 d, f = os.path.split(path)
1007 b, x = os.path.splitext(f)
1008 if x == ".py":
1009 f = b
1010 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001011 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001012 self.module = imp.load_module("device_specific", *info)
1013 except ImportError:
1014 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001015
1016 def _DoCall(self, function_name, *args, **kwargs):
1017 """Call the named function in the device-specific module, passing
1018 the given args and kwargs. The first argument to the call will be
1019 the DeviceSpecific object itself. If there is no module, or the
1020 module does not define the function, return the value of the
1021 'default' kwarg (which itself defaults to None)."""
1022 if self.module is None or not hasattr(self.module, function_name):
1023 return kwargs.get("default", None)
1024 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1025
1026 def FullOTA_Assertions(self):
1027 """Called after emitting the block of assertions at the top of a
1028 full OTA package. Implementations can add whatever additional
1029 assertions they like."""
1030 return self._DoCall("FullOTA_Assertions")
1031
Doug Zongkere5ff5902012-01-17 10:55:37 -08001032 def FullOTA_InstallBegin(self):
1033 """Called at the start of full OTA installation."""
1034 return self._DoCall("FullOTA_InstallBegin")
1035
Doug Zongker05d3dea2009-06-22 11:32:31 -07001036 def FullOTA_InstallEnd(self):
1037 """Called at the end of full OTA installation; typically this is
1038 used to install the image for the device's baseband processor."""
1039 return self._DoCall("FullOTA_InstallEnd")
1040
1041 def IncrementalOTA_Assertions(self):
1042 """Called after emitting the block of assertions at the top of an
1043 incremental OTA package. Implementations can add whatever
1044 additional assertions they like."""
1045 return self._DoCall("IncrementalOTA_Assertions")
1046
Doug Zongkere5ff5902012-01-17 10:55:37 -08001047 def IncrementalOTA_VerifyBegin(self):
1048 """Called at the start of the verification phase of incremental
1049 OTA installation; additional checks can be placed here to abort
1050 the script before any changes are made."""
1051 return self._DoCall("IncrementalOTA_VerifyBegin")
1052
Doug Zongker05d3dea2009-06-22 11:32:31 -07001053 def IncrementalOTA_VerifyEnd(self):
1054 """Called at the end of the verification phase of incremental OTA
1055 installation; additional checks can be placed here to abort the
1056 script before any changes are made."""
1057 return self._DoCall("IncrementalOTA_VerifyEnd")
1058
Doug Zongkere5ff5902012-01-17 10:55:37 -08001059 def IncrementalOTA_InstallBegin(self):
1060 """Called at the start of incremental OTA installation (after
1061 verification is complete)."""
1062 return self._DoCall("IncrementalOTA_InstallBegin")
1063
Doug Zongker05d3dea2009-06-22 11:32:31 -07001064 def IncrementalOTA_InstallEnd(self):
1065 """Called at the end of incremental OTA installation; typically
1066 this is used to install the image for the device's baseband
1067 processor."""
1068 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001069
1070class File(object):
1071 def __init__(self, name, data):
1072 self.name = name
1073 self.data = data
1074 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001075 self.sha1 = sha1(data).hexdigest()
1076
1077 @classmethod
1078 def FromLocalFile(cls, name, diskname):
1079 f = open(diskname, "rb")
1080 data = f.read()
1081 f.close()
1082 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001083
1084 def WriteToTemp(self):
1085 t = tempfile.NamedTemporaryFile()
1086 t.write(self.data)
1087 t.flush()
1088 return t
1089
Geremy Condra36bd3652014-02-06 19:45:10 -08001090 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001091 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001092
1093DIFF_PROGRAM_BY_EXT = {
1094 ".gz" : "imgdiff",
1095 ".zip" : ["imgdiff", "-z"],
1096 ".jar" : ["imgdiff", "-z"],
1097 ".apk" : ["imgdiff", "-z"],
1098 ".img" : "imgdiff",
1099 }
1100
1101class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001102 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001103 self.tf = tf
1104 self.sf = sf
1105 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001106 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001107
1108 def ComputePatch(self):
1109 """Compute the patch (as a string of data) needed to turn sf into
1110 tf. Returns the same tuple as GetPatch()."""
1111
1112 tf = self.tf
1113 sf = self.sf
1114
Doug Zongker24cd2802012-08-14 16:36:15 -07001115 if self.diff_program:
1116 diff_program = self.diff_program
1117 else:
1118 ext = os.path.splitext(tf.name)[1]
1119 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001120
1121 ttemp = tf.WriteToTemp()
1122 stemp = sf.WriteToTemp()
1123
1124 ext = os.path.splitext(tf.name)[1]
1125
1126 try:
1127 ptemp = tempfile.NamedTemporaryFile()
1128 if isinstance(diff_program, list):
1129 cmd = copy.copy(diff_program)
1130 else:
1131 cmd = [diff_program]
1132 cmd.append(stemp.name)
1133 cmd.append(ttemp.name)
1134 cmd.append(ptemp.name)
1135 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001136 err = []
1137 def run():
1138 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001139 if e:
1140 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001141 th = threading.Thread(target=run)
1142 th.start()
1143 th.join(timeout=300) # 5 mins
1144 if th.is_alive():
1145 print "WARNING: diff command timed out"
1146 p.terminate()
1147 th.join(5)
1148 if th.is_alive():
1149 p.kill()
1150 th.join()
1151
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001152 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001153 print "WARNING: failure running %s:\n%s\n" % (
1154 diff_program, "".join(err))
1155 self.patch = None
1156 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001157 diff = ptemp.read()
1158 finally:
1159 ptemp.close()
1160 stemp.close()
1161 ttemp.close()
1162
1163 self.patch = diff
1164 return self.tf, self.sf, self.patch
1165
1166
1167 def GetPatch(self):
1168 """Return a tuple (target_file, source_file, patch_data).
1169 patch_data may be None if ComputePatch hasn't been called, or if
1170 computing the patch failed."""
1171 return self.tf, self.sf, self.patch
1172
1173
1174def ComputeDifferences(diffs):
1175 """Call ComputePatch on all the Difference objects in 'diffs'."""
1176 print len(diffs), "diffs to compute"
1177
1178 # Do the largest files first, to try and reduce the long-pole effect.
1179 by_size = [(i.tf.size, i) for i in diffs]
1180 by_size.sort(reverse=True)
1181 by_size = [i[1] for i in by_size]
1182
1183 lock = threading.Lock()
1184 diff_iter = iter(by_size) # accessed under lock
1185
1186 def worker():
1187 try:
1188 lock.acquire()
1189 for d in diff_iter:
1190 lock.release()
1191 start = time.time()
1192 d.ComputePatch()
1193 dur = time.time() - start
1194 lock.acquire()
1195
1196 tf, sf, patch = d.GetPatch()
1197 if sf.name == tf.name:
1198 name = tf.name
1199 else:
1200 name = "%s (%s)" % (tf.name, sf.name)
1201 if patch is None:
1202 print "patching failed! %s" % (name,)
1203 else:
1204 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1205 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1206 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001207 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001208 print e
1209 raise
1210
1211 # start worker threads; wait for them all to finish.
1212 threads = [threading.Thread(target=worker)
1213 for i in range(OPTIONS.worker_threads)]
1214 for th in threads:
1215 th.start()
1216 while threads:
1217 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001218
1219
Dan Albert8b72aef2015-03-23 19:13:21 -07001220class BlockDifference(object):
1221 def __init__(self, partition, tgt, src=None, check_first_block=False,
1222 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001223 self.tgt = tgt
1224 self.src = src
1225 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001226 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001227
Tao Baoff777812015-05-12 11:42:31 -07001228 # Due to http://b/20939131, check_first_block is disabled temporarily.
1229 assert not self.check_first_block
1230
Tao Baodd2a5892015-03-12 12:32:37 -07001231 if version is None:
1232 version = 1
1233 if OPTIONS.info_dict:
1234 version = max(
1235 int(i) for i in
1236 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1237 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001238
1239 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001240 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001241 tmpdir = tempfile.mkdtemp()
1242 OPTIONS.tempfiles.append(tmpdir)
1243 self.path = os.path.join(tmpdir, partition)
1244 b.Compute(self.path)
1245
1246 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1247
1248 def WriteScript(self, script, output_zip, progress=None):
1249 if not self.src:
1250 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001251 script.Print("Patching %s image unconditionally..." % (self.partition,))
1252 else:
1253 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001254
Dan Albert8b72aef2015-03-23 19:13:21 -07001255 if progress:
1256 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001257 self._WriteUpdate(script, output_zip)
Tao Bao68658c02015-06-01 13:40:49 -07001258 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001259
1260 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001261 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001262 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001263 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001264 else:
Tao Baoff777812015-05-12 11:42:31 -07001265 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1266 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001267 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001268 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1269 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001270 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001271 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baoff777812015-05-12 11:42:31 -07001272 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001273 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001274 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001275 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Baoff777812015-05-12 11:42:31 -07001276 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001277 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001278 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001279
Tao Baodd2a5892015-03-12 12:32:37 -07001280 # When generating incrementals for the system and vendor partitions,
1281 # explicitly check the first block (which contains the superblock) of
1282 # the partition to see if it's what we expect. If this check fails,
1283 # give an explicit log message about the partition having been
1284 # remounted R/W (the most likely explanation) and the need to flash to
1285 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001286 if self.check_first_block:
1287 self._CheckFirstBlock(script)
1288
Tao Baodd2a5892015-03-12 12:32:37 -07001289 # Abort the OTA update. Note that the incremental OTA cannot be applied
1290 # even if it may match the checksum of the target partition.
1291 # a) If version < 3, operations like move and erase will make changes
1292 # unconditionally and damage the partition.
1293 # b) If version >= 3, it won't even reach here.
1294 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1295 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001296
Tao Bao68658c02015-06-01 13:40:49 -07001297 def _WritePostInstallVerifyScript(self, script):
1298 partition = self.partition
1299 script.Print('Verifying the updated %s image...' % (partition,))
1300 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1301 ranges = self.tgt.care_map
1302 ranges_str = ranges.to_string_raw()
1303 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1304 self.device, ranges_str,
1305 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001306
1307 # Bug: 20881595
1308 # Verify that extended blocks are really zeroed out.
1309 if self.tgt.extended:
1310 ranges_str = self.tgt.extended.to_string_raw()
1311 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1312 self.device, ranges_str,
1313 self._HashZeroBlocks(self.tgt.extended.size())))
1314 script.Print('Verified the updated %s image.' % (partition,))
1315 script.AppendExtra(
1316 'else\n'
1317 ' abort("%s partition has unexpected non-zero contents after OTA '
1318 'update");\n'
1319 'endif;' % (partition,))
1320 else:
1321 script.Print('Verified the updated %s image.' % (partition,))
1322
Tao Bao68658c02015-06-01 13:40:49 -07001323 script.AppendExtra(
1324 'else\n'
1325 ' abort("%s partition has unexpected contents after OTA update");\n'
1326 'endif;' % (partition,))
1327
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001328 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001329 ZipWrite(output_zip,
1330 '{}.transfer.list'.format(self.path),
1331 '{}.transfer.list'.format(self.partition))
1332 ZipWrite(output_zip,
1333 '{}.new.dat'.format(self.path),
1334 '{}.new.dat'.format(self.partition))
1335 ZipWrite(output_zip,
1336 '{}.patch.dat'.format(self.path),
1337 '{}.patch.dat'.format(self.partition),
1338 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001339
Dan Albert8e0178d2015-01-27 15:53:15 -08001340 call = ('block_image_update("{device}", '
1341 'package_extract_file("{partition}.transfer.list"), '
1342 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1343 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001344 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001345
Dan Albert8b72aef2015-03-23 19:13:21 -07001346 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001347 data = source.ReadRangeSet(ranges)
1348 ctx = sha1()
1349
1350 for p in data:
1351 ctx.update(p)
1352
1353 return ctx.hexdigest()
1354
Tao Baoe9b61912015-07-09 17:37:49 -07001355 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1356 """Return the hash value for all zero blocks."""
1357 zero_block = '\x00' * 4096
1358 ctx = sha1()
1359 for _ in range(num_blocks):
1360 ctx.update(zero_block)
1361
1362 return ctx.hexdigest()
1363
Tao Baoff777812015-05-12 11:42:31 -07001364 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1365 # remounting R/W. Will change the checking to a finer-grained way to
1366 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001367 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001368 r = rangelib.RangeSet((0, 1))
1369 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001370
1371 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1372 'abort("%s has been remounted R/W; '
1373 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001374 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001375 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001376
1377DataImage = blockimgdiff.DataImage
1378
1379
Doug Zongker96a57e72010-09-26 14:57:41 -07001380# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001381PARTITION_TYPES = {
1382 "yaffs2": "MTD",
1383 "mtd": "MTD",
1384 "ext4": "EMMC",
1385 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001386 "f2fs": "EMMC",
1387 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001388}
Doug Zongker96a57e72010-09-26 14:57:41 -07001389
1390def GetTypeAndDevice(mount_point, info):
1391 fstab = info["fstab"]
1392 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001393 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1394 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001395 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001396 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001397
1398
1399def ParseCertificate(data):
1400 """Parse a PEM-format certificate."""
1401 cert = []
1402 save = False
1403 for line in data.split("\n"):
1404 if "--END CERTIFICATE--" in line:
1405 break
1406 if save:
1407 cert.append(line)
1408 if "--BEGIN CERTIFICATE--" in line:
1409 save = True
1410 cert = "".join(cert).decode('base64')
1411 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001412
Doug Zongker412c02f2014-02-13 10:58:24 -08001413def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1414 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001415 """Generate a binary patch that creates the recovery image starting
1416 with the boot image. (Most of the space in these images is just the
1417 kernel, which is identical for the two, so the resulting patch
1418 should be efficient.) Add it to the output zip, along with a shell
1419 script that is run from init.rc on first boot to actually do the
1420 patching and install the new recovery image.
1421
1422 recovery_img and boot_img should be File objects for the
1423 corresponding images. info should be the dictionary returned by
1424 common.LoadInfoDict() on the input target_files.
1425 """
1426
Doug Zongker412c02f2014-02-13 10:58:24 -08001427 if info_dict is None:
1428 info_dict = OPTIONS.info_dict
1429
Tao Baof2cffbd2015-07-22 12:33:18 -07001430 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001431 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001432
Tao Baof2cffbd2015-07-22 12:33:18 -07001433 if full_recovery_image:
1434 output_sink("etc/recovery.img", recovery_img.data)
1435
1436 else:
1437 diff_program = ["imgdiff"]
1438 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1439 if os.path.exists(path):
1440 diff_program.append("-b")
1441 diff_program.append(path)
1442 bonus_args = "-b /system/etc/recovery-resource.dat"
1443 else:
1444 bonus_args = ""
1445
1446 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1447 _, _, patch = d.ComputePatch()
1448 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001449
Dan Albertebb19aa2015-03-27 19:11:53 -07001450 try:
1451 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1452 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1453 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001454 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001455
Tao Baof2cffbd2015-07-22 12:33:18 -07001456 if full_recovery_image:
1457 sh = """#!/system/bin/sh
1458if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1459 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"
1460else
1461 log -t recovery "Recovery image already installed"
1462fi
1463""" % {'type': recovery_type,
1464 'device': recovery_device,
1465 'sha1': recovery_img.sha1,
1466 'size': recovery_img.size}
1467 else:
1468 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001469if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1470 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"
1471else
1472 log -t recovery "Recovery image already installed"
1473fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001474""" % {'boot_size': boot_img.size,
1475 'boot_sha1': boot_img.sha1,
1476 'recovery_size': recovery_img.size,
1477 'recovery_sha1': recovery_img.sha1,
1478 'boot_type': boot_type,
1479 'boot_device': boot_device,
1480 'recovery_type': recovery_type,
1481 'recovery_device': recovery_device,
1482 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001483
1484 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001485 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001486 # target-files expects it to be, and put it there.
1487 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001488 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001489 if system_root_image:
1490 init_rc_dir = os.path.join(input_dir, "ROOT")
1491 else:
1492 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001493 init_rc_files = os.listdir(init_rc_dir)
1494 for init_rc_file in init_rc_files:
1495 if (not init_rc_file.startswith('init.') or
1496 not init_rc_file.endswith('.rc')):
1497 continue
1498
1499 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001500 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001501 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001502 if m:
1503 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001504 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001505 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001506
1507 if found:
1508 break
1509
1510 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001511
1512 output_sink(sh_location, sh)