blob: e37b0f8ca769431e5e4441c3e5b605da4a22ee15 [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
63
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
Dan Albert8b72aef2015-03-23 19:13:21 -0700100def LoadInfoDict(input_file):
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
Doug Zongker37974732010-09-16 17:44:38 -0700151 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700153 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not line:
155 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700156 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if not value:
158 continue
Doug Zongker37974732010-09-16 17:44:38 -0700159 if name == "blocksize":
160 d[name] = value
161 else:
162 d[name + "_size"] = value
163 except KeyError:
164 pass
165
166 def makeint(key):
167 if key in d:
168 d[key] = int(d[key], 0)
169
170 makeint("recovery_api_version")
171 makeint("blocksize")
172 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700173 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700174 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700175 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("recovery_size")
177 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800178 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700179
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
181 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 return d
183
Doug Zongkerc9253822014-02-04 12:17:58 -0800184def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 except KeyError:
188 print "Warning: could not find SYSTEM/build.prop in %s" % zip
189 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700190 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191
Michael Runge6e836112014-04-15 17:40:21 -0700192def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700194 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700196 if not line or line.startswith("#"):
197 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700198 if "=" in line:
199 name, value = line.split("=", 1)
200 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700201 return d
202
Doug Zongkerc9253822014-02-04 12:17:58 -0800203def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700205 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700206 self.mount_point = mount_point
207 self.fs_type = fs_type
208 self.device = device
209 self.length = length
210 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700211 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212
213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700215 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700217 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800219 if fstab_version == 1:
220 d = {}
221 for line in data.split("\n"):
222 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not line or line.startswith("#"):
224 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700226 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800228 options = None
229 if len(pieces) >= 4:
230 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 if len(pieces) >= 5:
233 options = pieces[4]
234 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800236 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800237 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700238 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700239
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 mount_point = pieces[0]
241 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 if options:
243 options = options.split(",")
244 for i in options:
245 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249
Dan Albert8b72aef2015-03-23 19:13:21 -0700250 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
251 device=pieces[2], length=length,
252 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253
254 elif fstab_version == 2:
255 d = {}
256 for line in data.split("\n"):
257 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 if not line or line.startswith("#"):
259 continue
Tao Bao548eb762015-06-10 12:32:41 -0700260 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 pieces = line.split()
262 if len(pieces) != 5:
263 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
264
265 # Ignore entries that are managed by vold
266 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 if "voldmanaged=" in options:
268 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269
270 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800272 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:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800276 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277 # Ignore all unknown options in the unified fstab
278 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800279
Tao Bao548eb762015-06-10 12:32:41 -0700280 mount_flags = pieces[3]
281 # Honor the SELinux context if present.
282 context = None
283 for i in mount_flags.split(","):
284 if i.startswith("context="):
285 context = i
286
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 mount_point = pieces[1]
288 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700289 device=pieces[0], length=length,
290 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291
292 else:
293 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
294
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295 return d
296
297
Doug Zongker37974732010-09-16 17:44:38 -0700298def DumpInfoDict(d):
299 for k, v in sorted(d.items()):
300 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700301
Dan Albert8b72aef2015-03-23 19:13:21 -0700302
Doug Zongkerd5131602012-08-02 14:46:42 -0700303def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700304 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700305 'sourcedir'), and turn them into a boot image. Return the image
306 data, or None if sourcedir does not appear to contains files for
307 building the requested image."""
308
309 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
310 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
311 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700312
Doug Zongkerd5131602012-08-02 14:46:42 -0700313 if info_dict is None:
314 info_dict = OPTIONS.info_dict
315
Doug Zongkereef39442009-04-02 12:14:19 -0700316 ramdisk_img = tempfile.NamedTemporaryFile()
317 img = tempfile.NamedTemporaryFile()
318
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700319 if os.access(fs_config_file, os.F_OK):
320 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
321 else:
322 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
323 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700324 p2 = Run(["minigzip"],
325 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700326
327 p2.wait()
328 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700329 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
330 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700331
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000332 """check if uboot is requested"""
333 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200334 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000335 cmd = ["mkimage"]
336 for argument in open(fn).read().rstrip("\n").split(" "):
337 cmd.append(argument)
338 cmd.append("-d")
339 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
340 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200341
Tao Baod95e9fd2015-03-29 23:07:41 -0700342 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000343 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
344 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
345 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700346
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000347 fn = os.path.join(sourcedir, "second")
348 if os.access(fn, os.F_OK):
349 cmd.append("--second")
350 cmd.append(fn)
351
352 fn = os.path.join(sourcedir, "cmdline")
353 if os.access(fn, os.F_OK):
354 cmd.append("--cmdline")
355 cmd.append(open(fn).read().rstrip("\n"))
356
357 fn = os.path.join(sourcedir, "base")
358 if os.access(fn, os.F_OK):
359 cmd.append("--base")
360 cmd.append(open(fn).read().rstrip("\n"))
361
362 fn = os.path.join(sourcedir, "tagsaddr")
363 if os.access(fn, os.F_OK):
364 cmd.append("--tags-addr")
365 cmd.append(open(fn).read().rstrip("\n"))
366
367 fn = os.path.join(sourcedir, "ramdisk_offset")
368 if os.access(fn, os.F_OK):
369 cmd.append("--ramdisk_offset")
370 cmd.append(open(fn).read().rstrip("\n"))
371
372 fn = os.path.join(sourcedir, "dt_args")
373 if os.access(fn, os.F_OK):
374 cmd.append("--dt")
375 cmd.append(open(fn).read().rstrip("\n"))
376
377 fn = os.path.join(sourcedir, "pagesize")
378 if os.access(fn, os.F_OK):
379 cmd.append("--pagesize")
380 cmd.append(open(fn).read().rstrip("\n"))
381
382 args = info_dict.get("mkbootimg_args", None)
383 if args and args.strip():
384 cmd.extend(shlex.split(args))
385
386 img_unsigned = None
387 if info_dict.get("vboot", None):
388 img_unsigned = tempfile.NamedTemporaryFile()
389 cmd.extend(["--ramdisk", ramdisk_img.name,
390 "--output", img_unsigned.name])
391 else:
392 cmd.extend(["--ramdisk", ramdisk_img.name,
393 "--output", img.name])
394
Doug Zongker38a649f2009-06-17 09:07:09 -0700395 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700396 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 assert p.returncode == 0, "mkbootimg of %s image failed" % (
398 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100400 if (info_dict.get("boot_signer", None) == "true" and
401 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700402 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700403 cmd = [OPTIONS.boot_signer_path]
404 cmd.extend(OPTIONS.boot_signer_args)
405 cmd.extend([path, img.name,
406 info_dict["verity_key"] + ".pk8",
407 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700408 p = Run(cmd, stdout=subprocess.PIPE)
409 p.communicate()
410 assert p.returncode == 0, "boot_signer of %s image failed" % path
411
Tao Baod95e9fd2015-03-29 23:07:41 -0700412 # Sign the image if vboot is non-empty.
413 elif info_dict.get("vboot", None):
414 path = "/" + os.path.basename(sourcedir).lower()
415 img_keyblock = tempfile.NamedTemporaryFile()
416 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
417 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
418 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
419 img.name]
420 p = Run(cmd, stdout=subprocess.PIPE)
421 p.communicate()
422 assert p.returncode == 0, "vboot_signer of %s image failed" % path
423
Tao Baof3282b42015-04-01 11:21:55 -0700424 # Clean up the temp files.
425 img_unsigned.close()
426 img_keyblock.close()
427
Doug Zongkereef39442009-04-02 12:14:19 -0700428 img.seek(os.SEEK_SET, 0)
429 data = img.read()
430
431 ramdisk_img.close()
432 img.close()
433
434 return data
435
436
Doug Zongkerd5131602012-08-02 14:46:42 -0700437def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
438 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800439 """Return a File object (with name 'name') with the desired bootable
440 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700441 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
442 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800443 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700444
Doug Zongker55d93282011-01-25 17:03:34 -0800445 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
446 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700447 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800448 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700449
450 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
451 if os.path.exists(prebuilt_path):
452 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
453 return File.FromLocalFile(name, prebuilt_path)
454
455 print "building image from target_files %s..." % (tree_subdir,)
456 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
457 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
458 os.path.join(unpack_dir, fs_config),
459 info_dict)
460 if data:
461 return File(name, data)
462 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800463
Doug Zongkereef39442009-04-02 12:14:19 -0700464
Doug Zongker75f17362009-12-08 13:46:44 -0800465def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800466 """Unzip the given archive into a temporary directory and return the name.
467
468 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
469 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
470
471 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
472 main file), open for reading.
473 """
Doug Zongkereef39442009-04-02 12:14:19 -0700474
475 tmp = tempfile.mkdtemp(prefix="targetfiles-")
476 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800477
478 def unzip_to_dir(filename, dirname):
Dan Pasanen17341b12014-12-21 23:18:16 -0600479 cmd = ["rm", "-rf", dirname + filename, "targetfiles-*"]
Doug Zongker55d93282011-01-25 17:03:34 -0800480 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
481 if pattern is not None:
482 cmd.append(pattern)
483 p = Run(cmd, stdout=subprocess.PIPE)
484 p.communicate()
485 if p.returncode != 0:
486 raise ExternalError("failed to unzip input target-files \"%s\"" %
487 (filename,))
488
489 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
490 if m:
491 unzip_to_dir(m.group(1), tmp)
492 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
493 filename = m.group(1)
494 else:
495 unzip_to_dir(filename, tmp)
496
497 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700498
499
500def GetKeyPasswords(keylist):
501 """Given a list of keys, prompt the user to enter passwords for
502 those which require them. Return a {key: password} dict. password
503 will be None if the key has no password."""
504
Doug Zongker8ce7c252009-05-22 13:34:54 -0700505 no_passwords = []
506 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700507 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700508 devnull = open("/dev/null", "w+b")
509 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800510 # We don't need a password for things that aren't really keys.
511 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700512 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700513 continue
514
T.R. Fullhart37e10522013-03-18 10:31:26 -0700515 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700516 "-inform", "DER", "-nocrypt"],
517 stdin=devnull.fileno(),
518 stdout=devnull.fileno(),
519 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700520 p.communicate()
521 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700522 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700523 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700524 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700525 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
526 "-inform", "DER", "-passin", "pass:"],
527 stdin=devnull.fileno(),
528 stdout=devnull.fileno(),
529 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700530 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700531 if p.returncode == 0:
532 # Encrypted key with empty string as password.
533 key_passwords[k] = ''
534 elif stderr.startswith('Error decrypting key'):
535 # Definitely encrypted key.
536 # It would have said "Error reading key" if it didn't parse correctly.
537 need_passwords.append(k)
538 else:
539 # Potentially, a type of key that openssl doesn't understand.
540 # We'll let the routines in signapk.jar handle it.
541 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700542 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700543
T.R. Fullhart37e10522013-03-18 10:31:26 -0700544 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700545 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700546 return key_passwords
547
548
Doug Zongker951495f2009-08-14 12:44:19 -0700549def SignFile(input_name, output_name, key, password, align=None,
550 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700551 """Sign the input_name zip/jar/apk, producing output_name. Use the
552 given key and password (the latter may be None if the key does not
553 have a password.
554
555 If align is an integer > 1, zipalign is run to align stored files in
556 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700557
558 If whole_file is true, use the "-w" option to SignApk to embed a
559 signature that covers the whole file in the archive comment of the
560 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700561 """
Doug Zongker951495f2009-08-14 12:44:19 -0700562
Doug Zongkereef39442009-04-02 12:14:19 -0700563 if align == 0 or align == 1:
564 align = None
565
566 if align:
567 temp = tempfile.NamedTemporaryFile()
568 sign_name = temp.name
569 else:
570 sign_name = output_name
571
Baligh Uddin339ee492014-09-05 11:18:07 -0700572 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700573 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
574 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700575 if whole_file:
576 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700577 cmd.extend([key + OPTIONS.public_key_suffix,
578 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700579 input_name, sign_name])
580
581 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700582 if password is not None:
583 password += "\n"
584 p.communicate(password)
585 if p.returncode != 0:
586 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
587
588 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700589 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700590 p.communicate()
591 if p.returncode != 0:
592 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
593 temp.close()
594
595
Doug Zongker37974732010-09-16 17:44:38 -0700596def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700597 """Check the data string passed against the max size limit, if
598 any, for the given target. Raise exception if the data is too big.
599 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700600
Dan Albert8b72aef2015-03-23 19:13:21 -0700601 if target.endswith(".img"):
602 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700603 mount_point = "/" + target
604
Ying Wangf8824af2014-06-03 14:07:27 -0700605 fs_type = None
606 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700607 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700608 if mount_point == "/userdata":
609 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700610 p = info_dict["fstab"][mount_point]
611 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800612 device = p.device
613 if "/" in device:
614 device = device[device.rfind("/")+1:]
615 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700616 if not fs_type or not limit:
617 return
Doug Zongkereef39442009-04-02 12:14:19 -0700618
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700619 if fs_type == "yaffs2":
620 # image size should be increased by 1/64th to account for the
621 # spare area (64 bytes per 2k page)
622 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800623 size = len(data)
624 pct = float(size) * 100.0 / limit
625 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
626 if pct >= 99.0:
627 raise ExternalError(msg)
628 elif pct >= 95.0:
629 print
630 print " WARNING: ", msg
631 print
632 elif OPTIONS.verbose:
633 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700634
635
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800636def ReadApkCerts(tf_zip):
637 """Given a target_files ZipFile, parse the META/apkcerts.txt file
638 and return a {package: cert} dict."""
639 certmap = {}
640 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
641 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700642 if not line:
643 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800644 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
645 r'private_key="(.*)"$', line)
646 if m:
647 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700648 public_key_suffix_len = len(OPTIONS.public_key_suffix)
649 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800650 if cert in SPECIAL_CERT_STRINGS and not privkey:
651 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700652 elif (cert.endswith(OPTIONS.public_key_suffix) and
653 privkey.endswith(OPTIONS.private_key_suffix) and
654 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
655 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800656 else:
657 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
658 return certmap
659
660
Doug Zongkereef39442009-04-02 12:14:19 -0700661COMMON_DOCSTRING = """
662 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700663 Prepend <dir>/bin to the list of places to search for binaries
664 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700665
Doug Zongker05d3dea2009-06-22 11:32:31 -0700666 -s (--device_specific) <file>
667 Path to the python module containing device-specific
668 releasetools code.
669
Doug Zongker8bec09e2009-11-30 15:37:14 -0800670 -x (--extra) <key=value>
671 Add a key/value pair to the 'extras' dict, which device-specific
672 extension code may look at.
673
Doug Zongkereef39442009-04-02 12:14:19 -0700674 -v (--verbose)
675 Show command lines being executed.
676
677 -h (--help)
678 Display this usage message and exit.
679"""
680
681def Usage(docstring):
682 print docstring.rstrip("\n")
683 print COMMON_DOCSTRING
684
685
686def ParseOptions(argv,
687 docstring,
688 extra_opts="", extra_long_opts=(),
689 extra_option_handler=None):
690 """Parse the options in argv and return any arguments that aren't
691 flags. docstring is the calling module's docstring, to be displayed
692 for errors and -h. extra_opts and extra_long_opts are for flags
693 defined by the caller, which are processed by passing them to
694 extra_option_handler."""
695
696 try:
697 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800698 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700699 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700700 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700701 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
702 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800703 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700704 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700705 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700706 Usage(docstring)
707 print "**", str(err), "**"
708 sys.exit(2)
709
Doug Zongkereef39442009-04-02 12:14:19 -0700710 for o, a in opts:
711 if o in ("-h", "--help"):
712 Usage(docstring)
713 sys.exit()
714 elif o in ("-v", "--verbose"):
715 OPTIONS.verbose = True
716 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700717 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700718 elif o in ("--signapk_path",):
719 OPTIONS.signapk_path = a
720 elif o in ("--extra_signapk_args",):
721 OPTIONS.extra_signapk_args = shlex.split(a)
722 elif o in ("--java_path",):
723 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700724 elif o in ("--java_args",):
725 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700726 elif o in ("--public_key_suffix",):
727 OPTIONS.public_key_suffix = a
728 elif o in ("--private_key_suffix",):
729 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800730 elif o in ("--boot_signer_path",):
731 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700732 elif o in ("--boot_signer_args",):
733 OPTIONS.boot_signer_args = shlex.split(a)
734 elif o in ("--verity_signer_path",):
735 OPTIONS.verity_signer_path = a
736 elif o in ("--verity_signer_args",):
737 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700738 elif o in ("-s", "--device_specific"):
739 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800740 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800741 key, value = a.split("=", 1)
742 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700743 else:
744 if extra_option_handler is None or not extra_option_handler(o, a):
745 assert False, "unknown option \"%s\"" % (o,)
746
Doug Zongker85448772014-09-09 14:59:20 -0700747 if OPTIONS.search_path:
748 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
749 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700750
751 return args
752
753
Doug Zongkerfc44a512014-08-26 13:10:25 -0700754def MakeTempFile(prefix=None, suffix=None):
755 """Make a temp file and add it to the list of things to be deleted
756 when Cleanup() is called. Return the filename."""
757 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
758 os.close(fd)
759 OPTIONS.tempfiles.append(fn)
760 return fn
761
762
Doug Zongkereef39442009-04-02 12:14:19 -0700763def Cleanup():
764 for i in OPTIONS.tempfiles:
765 if os.path.isdir(i):
766 shutil.rmtree(i)
767 else:
768 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700769
770
771class PasswordManager(object):
772 def __init__(self):
773 self.editor = os.getenv("EDITOR", None)
774 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
775
776 def GetPasswords(self, items):
777 """Get passwords corresponding to each string in 'items',
778 returning a dict. (The dict may have keys in addition to the
779 values in 'items'.)
780
781 Uses the passwords in $ANDROID_PW_FILE if available, letting the
782 user edit that file to add more needed passwords. If no editor is
783 available, or $ANDROID_PW_FILE isn't define, prompts the user
784 interactively in the ordinary way.
785 """
786
787 current = self.ReadFile()
788
789 first = True
790 while True:
791 missing = []
792 for i in items:
793 if i not in current or not current[i]:
794 missing.append(i)
795 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700796 if not missing:
797 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700798
799 for i in missing:
800 current[i] = ""
801
802 if not first:
803 print "key file %s still missing some passwords." % (self.pwfile,)
804 answer = raw_input("try to edit again? [y]> ").strip()
805 if answer and answer[0] not in 'yY':
806 raise RuntimeError("key passwords unavailable")
807 first = False
808
809 current = self.UpdateAndReadFile(current)
810
Dan Albert8b72aef2015-03-23 19:13:21 -0700811 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 """Prompt the user to enter a value (password) for each key in
813 'current' whose value is fales. Returns a new dict with all the
814 values.
815 """
816 result = {}
817 for k, v in sorted(current.iteritems()):
818 if v:
819 result[k] = v
820 else:
821 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700822 result[k] = getpass.getpass(
823 "Enter password for %s key> " % k).strip()
824 if result[k]:
825 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700826 return result
827
828 def UpdateAndReadFile(self, current):
829 if not self.editor or not self.pwfile:
830 return self.PromptResult(current)
831
832 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700833 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700834 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
835 f.write("# (Additional spaces are harmless.)\n\n")
836
837 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700838 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
839 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700840 f.write("[[[ %s ]]] %s\n" % (v, k))
841 if not v and first_line is None:
842 # position cursor on first line with no password.
843 first_line = i + 4
844 f.close()
845
846 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
847 _, _ = p.communicate()
848
849 return self.ReadFile()
850
851 def ReadFile(self):
852 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700853 if self.pwfile is None:
854 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700855 try:
856 f = open(self.pwfile, "r")
857 for line in f:
858 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700859 if not line or line[0] == '#':
860 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700861 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
862 if not m:
863 print "failed to parse password file: ", line
864 else:
865 result[m.group(2)] = m.group(1)
866 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700867 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 if e.errno != errno.ENOENT:
869 print "error reading password file: ", str(e)
870 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700871
872
Dan Albert8e0178d2015-01-27 15:53:15 -0800873def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
874 compress_type=None):
875 import datetime
876
877 # http://b/18015246
878 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
879 # for files larger than 2GiB. We can work around this by adjusting their
880 # limit. Note that `zipfile.writestr()` will not work for strings larger than
881 # 2GiB. The Python interpreter sometimes rejects strings that large (though
882 # it isn't clear to me exactly what circumstances cause this).
883 # `zipfile.write()` must be used directly to work around this.
884 #
885 # This mess can be avoided if we port to python3.
886 saved_zip64_limit = zipfile.ZIP64_LIMIT
887 zipfile.ZIP64_LIMIT = (1 << 32) - 1
888
889 if compress_type is None:
890 compress_type = zip_file.compression
891 if arcname is None:
892 arcname = filename
893
894 saved_stat = os.stat(filename)
895
896 try:
897 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
898 # file to be zipped and reset it when we're done.
899 os.chmod(filename, perms)
900
901 # Use a fixed timestamp so the output is repeatable.
902 epoch = datetime.datetime.fromtimestamp(0)
903 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
904 os.utime(filename, (timestamp, timestamp))
905
906 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
907 finally:
908 os.chmod(filename, saved_stat.st_mode)
909 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
910 zipfile.ZIP64_LIMIT = saved_zip64_limit
911
912
Tao Bao58c1b962015-05-20 09:32:18 -0700913def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700914 compress_type=None):
915 """Wrap zipfile.writestr() function to work around the zip64 limit.
916
917 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
918 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
919 when calling crc32(bytes).
920
921 But it still works fine to write a shorter string into a large zip file.
922 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
923 when we know the string won't be too long.
924 """
925
926 saved_zip64_limit = zipfile.ZIP64_LIMIT
927 zipfile.ZIP64_LIMIT = (1 << 32) - 1
928
929 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
930 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700931 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700932 if perms is None:
933 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800934 else:
Tao Baof3282b42015-04-01 11:21:55 -0700935 zinfo = zinfo_or_arcname
936
937 # If compress_type is given, it overrides the value in zinfo.
938 if compress_type is not None:
939 zinfo.compress_type = compress_type
940
Tao Bao58c1b962015-05-20 09:32:18 -0700941 # If perms is given, it has a priority.
942 if perms is not None:
943 zinfo.external_attr = perms << 16
944
Tao Baof3282b42015-04-01 11:21:55 -0700945 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700946 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
947
Dan Albert8b72aef2015-03-23 19:13:21 -0700948 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700949 zipfile.ZIP64_LIMIT = saved_zip64_limit
950
951
952def ZipClose(zip_file):
953 # http://b/18015246
954 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
955 # central directory.
956 saved_zip64_limit = zipfile.ZIP64_LIMIT
957 zipfile.ZIP64_LIMIT = (1 << 32) - 1
958
959 zip_file.close()
960
961 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700962
963
964class DeviceSpecificParams(object):
965 module = None
966 def __init__(self, **kwargs):
967 """Keyword arguments to the constructor become attributes of this
968 object, which is passed to all functions in the device-specific
969 module."""
970 for k, v in kwargs.iteritems():
971 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800972 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700973
974 if self.module is None:
975 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700976 if not path:
977 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700978 try:
979 if os.path.isdir(path):
980 info = imp.find_module("releasetools", [path])
981 else:
982 d, f = os.path.split(path)
983 b, x = os.path.splitext(f)
984 if x == ".py":
985 f = b
986 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800987 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700988 self.module = imp.load_module("device_specific", *info)
989 except ImportError:
990 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700991
992 def _DoCall(self, function_name, *args, **kwargs):
993 """Call the named function in the device-specific module, passing
994 the given args and kwargs. The first argument to the call will be
995 the DeviceSpecific object itself. If there is no module, or the
996 module does not define the function, return the value of the
997 'default' kwarg (which itself defaults to None)."""
998 if self.module is None or not hasattr(self.module, function_name):
999 return kwargs.get("default", None)
1000 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1001
1002 def FullOTA_Assertions(self):
1003 """Called after emitting the block of assertions at the top of a
1004 full OTA package. Implementations can add whatever additional
1005 assertions they like."""
1006 return self._DoCall("FullOTA_Assertions")
1007
Doug Zongkere5ff5902012-01-17 10:55:37 -08001008 def FullOTA_InstallBegin(self):
1009 """Called at the start of full OTA installation."""
1010 return self._DoCall("FullOTA_InstallBegin")
1011
Doug Zongker05d3dea2009-06-22 11:32:31 -07001012 def FullOTA_InstallEnd(self):
1013 """Called at the end of full OTA installation; typically this is
1014 used to install the image for the device's baseband processor."""
1015 return self._DoCall("FullOTA_InstallEnd")
1016
M1chad27ccf72014-11-25 15:30:48 +01001017 def FullOTA_PostValidate(self):
1018 """Called after installing and validating /system; typically this is
1019 used to resize the system partition after a block based installation."""
1020 return self._DoCall("FullOTA_PostValidate")
1021
Doug Zongker05d3dea2009-06-22 11:32:31 -07001022 def IncrementalOTA_Assertions(self):
1023 """Called after emitting the block of assertions at the top of an
1024 incremental OTA package. Implementations can add whatever
1025 additional assertions they like."""
1026 return self._DoCall("IncrementalOTA_Assertions")
1027
Doug Zongkere5ff5902012-01-17 10:55:37 -08001028 def IncrementalOTA_VerifyBegin(self):
1029 """Called at the start of the verification phase of incremental
1030 OTA installation; additional checks can be placed here to abort
1031 the script before any changes are made."""
1032 return self._DoCall("IncrementalOTA_VerifyBegin")
1033
Doug Zongker05d3dea2009-06-22 11:32:31 -07001034 def IncrementalOTA_VerifyEnd(self):
1035 """Called at the end of the verification phase of incremental OTA
1036 installation; additional checks can be placed here to abort the
1037 script before any changes are made."""
1038 return self._DoCall("IncrementalOTA_VerifyEnd")
1039
Doug Zongkere5ff5902012-01-17 10:55:37 -08001040 def IncrementalOTA_InstallBegin(self):
1041 """Called at the start of incremental OTA installation (after
1042 verification is complete)."""
1043 return self._DoCall("IncrementalOTA_InstallBegin")
1044
Doug Zongker05d3dea2009-06-22 11:32:31 -07001045 def IncrementalOTA_InstallEnd(self):
1046 """Called at the end of incremental OTA installation; typically
1047 this is used to install the image for the device's baseband
1048 processor."""
1049 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001050
1051class File(object):
1052 def __init__(self, name, data):
1053 self.name = name
1054 self.data = data
1055 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001056 self.sha1 = sha1(data).hexdigest()
1057
1058 @classmethod
1059 def FromLocalFile(cls, name, diskname):
1060 f = open(diskname, "rb")
1061 data = f.read()
1062 f.close()
1063 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001064
1065 def WriteToTemp(self):
1066 t = tempfile.NamedTemporaryFile()
1067 t.write(self.data)
1068 t.flush()
1069 return t
1070
Geremy Condra36bd3652014-02-06 19:45:10 -08001071 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001072 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001073
1074DIFF_PROGRAM_BY_EXT = {
1075 ".gz" : "imgdiff",
1076 ".zip" : ["imgdiff", "-z"],
1077 ".jar" : ["imgdiff", "-z"],
1078 ".apk" : ["imgdiff", "-z"],
1079 ".img" : "imgdiff",
1080 }
1081
1082class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001083 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001084 self.tf = tf
1085 self.sf = sf
1086 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001087 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001088
1089 def ComputePatch(self):
1090 """Compute the patch (as a string of data) needed to turn sf into
1091 tf. Returns the same tuple as GetPatch()."""
1092
1093 tf = self.tf
1094 sf = self.sf
1095
Doug Zongker24cd2802012-08-14 16:36:15 -07001096 if self.diff_program:
1097 diff_program = self.diff_program
1098 else:
1099 ext = os.path.splitext(tf.name)[1]
1100 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001101
1102 ttemp = tf.WriteToTemp()
1103 stemp = sf.WriteToTemp()
1104
1105 ext = os.path.splitext(tf.name)[1]
1106
1107 try:
1108 ptemp = tempfile.NamedTemporaryFile()
1109 if isinstance(diff_program, list):
1110 cmd = copy.copy(diff_program)
1111 else:
1112 cmd = [diff_program]
1113 cmd.append(stemp.name)
1114 cmd.append(ttemp.name)
1115 cmd.append(ptemp.name)
1116 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001117 err = []
1118 def run():
1119 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001120 if e:
1121 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001122 th = threading.Thread(target=run)
1123 th.start()
1124 th.join(timeout=300) # 5 mins
1125 if th.is_alive():
1126 print "WARNING: diff command timed out"
1127 p.terminate()
1128 th.join(5)
1129 if th.is_alive():
1130 p.kill()
1131 th.join()
1132
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001133 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001134 print "WARNING: failure running %s:\n%s\n" % (
1135 diff_program, "".join(err))
1136 self.patch = None
1137 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001138 diff = ptemp.read()
1139 finally:
1140 ptemp.close()
1141 stemp.close()
1142 ttemp.close()
1143
1144 self.patch = diff
1145 return self.tf, self.sf, self.patch
1146
1147
1148 def GetPatch(self):
1149 """Return a tuple (target_file, source_file, patch_data).
1150 patch_data may be None if ComputePatch hasn't been called, or if
1151 computing the patch failed."""
1152 return self.tf, self.sf, self.patch
1153
1154
1155def ComputeDifferences(diffs):
1156 """Call ComputePatch on all the Difference objects in 'diffs'."""
1157 print len(diffs), "diffs to compute"
1158
1159 # Do the largest files first, to try and reduce the long-pole effect.
1160 by_size = [(i.tf.size, i) for i in diffs]
1161 by_size.sort(reverse=True)
1162 by_size = [i[1] for i in by_size]
1163
1164 lock = threading.Lock()
1165 diff_iter = iter(by_size) # accessed under lock
1166
1167 def worker():
1168 try:
1169 lock.acquire()
1170 for d in diff_iter:
1171 lock.release()
1172 start = time.time()
1173 d.ComputePatch()
1174 dur = time.time() - start
1175 lock.acquire()
1176
1177 tf, sf, patch = d.GetPatch()
1178 if sf.name == tf.name:
1179 name = tf.name
1180 else:
1181 name = "%s (%s)" % (tf.name, sf.name)
1182 if patch is None:
1183 print "patching failed! %s" % (name,)
1184 else:
1185 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1186 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1187 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001188 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001189 print e
1190 raise
1191
1192 # start worker threads; wait for them all to finish.
1193 threads = [threading.Thread(target=worker)
1194 for i in range(OPTIONS.worker_threads)]
1195 for th in threads:
1196 th.start()
1197 while threads:
1198 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001199
1200
Dan Albert8b72aef2015-03-23 19:13:21 -07001201class BlockDifference(object):
1202 def __init__(self, partition, tgt, src=None, check_first_block=False,
1203 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001204 self.tgt = tgt
1205 self.src = src
1206 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001207 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001208
Tao Bao5ece99d2015-05-12 11:42:31 -07001209 # Due to http://b/20939131, check_first_block is disabled temporarily.
1210 assert not self.check_first_block
1211
Tao Baodd2a5892015-03-12 12:32:37 -07001212 if version is None:
1213 version = 1
1214 if OPTIONS.info_dict:
1215 version = max(
1216 int(i) for i in
1217 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1218 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001219
1220 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001221 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001222 tmpdir = tempfile.mkdtemp()
1223 OPTIONS.tempfiles.append(tmpdir)
1224 self.path = os.path.join(tmpdir, partition)
1225 b.Compute(self.path)
1226
1227 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1228
1229 def WriteScript(self, script, output_zip, progress=None):
1230 if not self.src:
1231 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001232 script.Print("Patching %s image unconditionally..." % (self.partition,))
1233 else:
1234 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001235
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 if progress:
1237 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001238 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001239 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001240
1241 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001242 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001243 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001244 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001245 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001246 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1247 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001248 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001249 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1250 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001251 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001252 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001253 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001254 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001255 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001256 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001257 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001258 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001259 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001260
Tao Baodd2a5892015-03-12 12:32:37 -07001261 # When generating incrementals for the system and vendor partitions,
1262 # explicitly check the first block (which contains the superblock) of
1263 # the partition to see if it's what we expect. If this check fails,
1264 # give an explicit log message about the partition having been
1265 # remounted R/W (the most likely explanation) and the need to flash to
1266 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001267 if self.check_first_block:
1268 self._CheckFirstBlock(script)
1269
Tao Baodd2a5892015-03-12 12:32:37 -07001270 # Abort the OTA update. Note that the incremental OTA cannot be applied
1271 # even if it may match the checksum of the target partition.
1272 # a) If version < 3, operations like move and erase will make changes
1273 # unconditionally and damage the partition.
1274 # b) If version >= 3, it won't even reach here.
1275 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1276 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001277
Tao Bao5fcaaef2015-06-01 13:40:49 -07001278 def _WritePostInstallVerifyScript(self, script):
1279 partition = self.partition
1280 script.Print('Verifying the updated %s image...' % (partition,))
1281 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1282 ranges = self.tgt.care_map
1283 ranges_str = ranges.to_string_raw()
1284 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1285 self.device, ranges_str,
1286 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001287
1288 # Bug: 20881595
1289 # Verify that extended blocks are really zeroed out.
1290 if self.tgt.extended:
1291 ranges_str = self.tgt.extended.to_string_raw()
1292 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1293 self.device, ranges_str,
1294 self._HashZeroBlocks(self.tgt.extended.size())))
1295 script.Print('Verified the updated %s image.' % (partition,))
1296 script.AppendExtra(
1297 'else\n'
1298 ' abort("%s partition has unexpected non-zero contents after OTA '
1299 'update");\n'
1300 'endif;' % (partition,))
1301 else:
1302 script.Print('Verified the updated %s image.' % (partition,))
1303
Tao Bao5fcaaef2015-06-01 13:40:49 -07001304 script.AppendExtra(
1305 'else\n'
1306 ' abort("%s partition has unexpected contents after OTA update");\n'
1307 'endif;' % (partition,))
1308
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001309 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001310 ZipWrite(output_zip,
1311 '{}.transfer.list'.format(self.path),
1312 '{}.transfer.list'.format(self.partition))
1313 ZipWrite(output_zip,
1314 '{}.new.dat'.format(self.path),
1315 '{}.new.dat'.format(self.partition))
1316 ZipWrite(output_zip,
1317 '{}.patch.dat'.format(self.path),
1318 '{}.patch.dat'.format(self.partition),
1319 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001320
Dan Albert8e0178d2015-01-27 15:53:15 -08001321 call = ('block_image_update("{device}", '
1322 'package_extract_file("{partition}.transfer.list"), '
1323 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1324 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001325 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001326
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001328 data = source.ReadRangeSet(ranges)
1329 ctx = sha1()
1330
1331 for p in data:
1332 ctx.update(p)
1333
1334 return ctx.hexdigest()
1335
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001336 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1337 """Return the hash value for all zero blocks."""
1338 zero_block = '\x00' * 4096
1339 ctx = sha1()
1340 for _ in range(num_blocks):
1341 ctx.update(zero_block)
1342
1343 return ctx.hexdigest()
1344
Tao Bao5ece99d2015-05-12 11:42:31 -07001345 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1346 # remounting R/W. Will change the checking to a finer-grained way to
1347 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001348 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001349 r = rangelib.RangeSet((0, 1))
1350 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001351
1352 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1353 'abort("%s has been remounted R/W; '
1354 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001355 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001356 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001357
1358DataImage = blockimgdiff.DataImage
1359
1360
Doug Zongker96a57e72010-09-26 14:57:41 -07001361# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001362PARTITION_TYPES = {
1363 "yaffs2": "MTD",
1364 "mtd": "MTD",
1365 "ext4": "EMMC",
1366 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001367 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001368 "squashfs": "EMMC",
1369 "ext2": "EMMC",
1370 "ext3": "EMMC",
1371 "vfat": "EMMC" }
Dan Albert8b72aef2015-03-23 19:13:21 -07001372}
Doug Zongker96a57e72010-09-26 14:57:41 -07001373
1374def GetTypeAndDevice(mount_point, info):
1375 fstab = info["fstab"]
1376 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001377 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1378 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001379 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001380 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001381
1382
1383def ParseCertificate(data):
1384 """Parse a PEM-format certificate."""
1385 cert = []
1386 save = False
1387 for line in data.split("\n"):
1388 if "--END CERTIFICATE--" in line:
1389 break
1390 if save:
1391 cert.append(line)
1392 if "--BEGIN CERTIFICATE--" in line:
1393 save = True
1394 cert = "".join(cert).decode('base64')
1395 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001396
Doug Zongker412c02f2014-02-13 10:58:24 -08001397def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1398 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001399 """Generate a binary patch that creates the recovery image starting
1400 with the boot image. (Most of the space in these images is just the
1401 kernel, which is identical for the two, so the resulting patch
1402 should be efficient.) Add it to the output zip, along with a shell
1403 script that is run from init.rc on first boot to actually do the
1404 patching and install the new recovery image.
1405
1406 recovery_img and boot_img should be File objects for the
1407 corresponding images. info should be the dictionary returned by
1408 common.LoadInfoDict() on the input target_files.
1409 """
1410
Doug Zongker412c02f2014-02-13 10:58:24 -08001411 if info_dict is None:
1412 info_dict = OPTIONS.info_dict
1413
Doug Zongkerc9253822014-02-04 12:17:58 -08001414 diff_program = ["imgdiff"]
1415 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1416 if os.path.exists(path):
1417 diff_program.append("-b")
1418 diff_program.append(path)
1419 bonus_args = "-b /system/etc/recovery-resource.dat"
1420 else:
1421 bonus_args = ""
1422
1423 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1424 _, _, patch = d.ComputePatch()
1425 output_sink("recovery-from-boot.p", patch)
1426
Dan Albertebb19aa2015-03-27 19:11:53 -07001427 try:
1428 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1429 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1430 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001431 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001432
1433 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001434if [ -f /system/etc/recovery-transform.sh ]; then
1435 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1436fi
1437
Doug Zongkerc9253822014-02-04 12:17:58 -08001438if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1439 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"
1440else
1441 log -t recovery "Recovery image already installed"
1442fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001443""" % {'boot_size': boot_img.size,
1444 'boot_sha1': boot_img.sha1,
1445 'recovery_size': recovery_img.size,
1446 'recovery_sha1': recovery_img.sha1,
1447 'boot_type': boot_type,
1448 'boot_device': boot_device,
1449 'recovery_type': recovery_type,
1450 'recovery_device': recovery_device,
1451 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001452
1453 # The install script location moved from /system/etc to /system/bin
1454 # in the L release. Parse the init.rc file to find out where the
1455 # target-files expects it to be, and put it there.
1456 sh_location = "etc/install-recovery.sh"
1457 try:
1458 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1459 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001461 if m:
1462 sh_location = m.group(1)
1463 print "putting script in", sh_location
1464 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001465 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001466 print "failed to read init.rc: %s" % (e,)
1467
1468 output_sink(sh_location, sh)