blob: bf9656887f83a33bb518708c84e380e8c913e381 [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
Doug Zongkerb34fcce2014-09-11 09:34:56 -070033from rangelib import *
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Doug Zongker55d93282011-01-25 17:03:34 -080035try:
davidcad0bb92011-03-15 14:21:38 +000036 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037except ImportError:
davidcad0bb92011-03-15 14:21:38 +000038 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080039
Doug Zongkereef39442009-04-02 12:14:19 -070040# missing in Python 2.4 and before
41if not hasattr(os, "SEEK_SET"):
42 os.SEEK_SET = 0
43
44class Options(object): pass
45OPTIONS = Options()
Doug Zongker85448772014-09-09 14:59:20 -070046
47DEFAULT_SEARCH_PATH_BY_PLATFORM = {
48 "linux2": "out/host/linux-x86",
49 "darwin": "out/host/darwin-x86",
50 }
51OPTIONS.search_path = DEFAULT_SEARCH_PATH_BY_PLATFORM.get(sys.platform, None)
52
T.R. Fullhart37e10522013-03-18 10:31:26 -070053OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
54OPTIONS.extra_signapk_args = []
55OPTIONS.java_path = "java" # Use the one on the path by default.
Baligh Uddin339ee492014-09-05 11:18:07 -070056OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070057OPTIONS.public_key_suffix = ".x509.pem"
58OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070059OPTIONS.verbose = False
60OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070061OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080062OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070063OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070064
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080065
66# Values for "certificate" in apkcerts that mean special things.
67SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
68
69
Doug Zongkereef39442009-04-02 12:14:19 -070070class ExternalError(RuntimeError): pass
71
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Doug Zongkerc9253822014-02-04 12:17:58 -080097def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
102 if isinstance(input, zipfile.ZipFile):
103 return input.read(fn)
104 else:
105 path = os.path.join(input, *fn.split("/"))
106 try:
107 with open(path) as f:
108 return f.read()
109 except IOError, e:
110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800125 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700126 except KeyError:
127 # ok if flags don't exist
128 pass
129
130 if "recovery_api_version" not in d:
131 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800132 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700133 except KeyError:
134 raise ValueError("can't find recovery API version in input target-files")
135
136 if "tool_extensions" not in d:
137 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800138 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700139 except KeyError:
140 # ok if extensions don't exist
141 pass
142
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800143 if "fstab_version" not in d:
144 d["fstab_version"] = "1"
145
Doug Zongker37974732010-09-16 17:44:38 -0700146 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700148 for line in data.split("\n"):
149 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700150 name, value = line.split(" ", 1)
151 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700152 if name == "blocksize":
153 d[name] = value
154 else:
155 d[name + "_size"] = value
156 except KeyError:
157 pass
158
159 def makeint(key):
160 if key in d:
161 d[key] = int(d[key], 0)
162
163 makeint("recovery_api_version")
164 makeint("blocksize")
165 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700166 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700167 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700168 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700169 makeint("recovery_size")
170 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800171 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Doug Zongkerc9253822014-02-04 12:17:58 -0800173 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
174 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700175 return d
176
Doug Zongkerc9253822014-02-04 12:17:58 -0800177def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700178 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800179 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700180 except KeyError:
181 print "Warning: could not find SYSTEM/build.prop in %s" % zip
182 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700183 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184
Michael Runge6e836112014-04-15 17:40:21 -0700185def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700186 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700187 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188 line = line.strip()
189 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700190 if "=" in line:
191 name, value = line.split("=", 1)
192 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700193 return d
194
Doug Zongkerc9253822014-02-04 12:17:58 -0800195def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700196 class Partition(object):
197 pass
198
199 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800200 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700201 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800202 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700203 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800205 if fstab_version == 1:
206 d = {}
207 for line in data.split("\n"):
208 line = line.strip()
209 if not line or line.startswith("#"): continue
210 pieces = line.split()
211 if not (3 <= len(pieces) <= 4):
212 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700213
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800214 p = Partition()
215 p.mount_point = pieces[0]
216 p.fs_type = pieces[1]
217 p.device = pieces[2]
218 p.length = 0
219 options = None
220 if len(pieces) >= 4:
221 if pieces[3].startswith("/"):
222 p.device2 = pieces[3]
223 if len(pieces) >= 5:
224 options = pieces[4]
225 else:
226 p.device2 = None
227 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800228 else:
229 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700230
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 if options:
232 options = options.split(",")
233 for i in options:
234 if i.startswith("length="):
235 p.length = int(i[7:])
236 else:
237 print "%s: unknown option \"%s\"" % (p.mount_point, i)
238
239 d[p.mount_point] = p
240
241 elif fstab_version == 2:
242 d = {}
243 for line in data.split("\n"):
244 line = line.strip()
245 if not line or line.startswith("#"): continue
246 pieces = line.split()
247 if len(pieces) != 5:
248 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
249
250 # Ignore entries that are managed by vold
251 options = pieces[4]
252 if "voldmanaged=" in options: continue
253
254 # It's a good line, parse it
255 p = Partition()
256 p.device = pieces[0]
257 p.mount_point = pieces[1]
258 p.fs_type = pieces[2]
259 p.device2 = None
260 p.length = 0
261
Doug Zongker086cbb02011-02-17 15:54:20 -0800262 options = options.split(",")
263 for i in options:
264 if i.startswith("length="):
265 p.length = int(i[7:])
266 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 # Ignore all unknown options in the unified fstab
268 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800269
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270 d[p.mount_point] = p
271
272 else:
273 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
274
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700275 return d
276
277
Doug Zongker37974732010-09-16 17:44:38 -0700278def DumpInfoDict(d):
279 for k, v in sorted(d.items()):
280 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700281
Doug Zongkerd5131602012-08-02 14:46:42 -0700282def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700283 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700284 'sourcedir'), and turn them into a boot image. Return the image
285 data, or None if sourcedir does not appear to contains files for
286 building the requested image."""
287
288 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
289 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
290 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700291
Doug Zongkerd5131602012-08-02 14:46:42 -0700292 if info_dict is None:
293 info_dict = OPTIONS.info_dict
294
Doug Zongkereef39442009-04-02 12:14:19 -0700295 ramdisk_img = tempfile.NamedTemporaryFile()
296 img = tempfile.NamedTemporaryFile()
297
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700298 if os.access(fs_config_file, os.F_OK):
299 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
300 else:
301 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
302 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700303 p2 = Run(["minigzip"],
304 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700305
306 p2.wait()
307 p1.wait()
308 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700309 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700310
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800311 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
312 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
313
314 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700315
Benoit Fradina45a8682014-07-14 21:00:43 +0200316 fn = os.path.join(sourcedir, "second")
317 if os.access(fn, os.F_OK):
318 cmd.append("--second")
319 cmd.append(fn)
320
Doug Zongker171f1cd2009-06-15 22:36:37 -0700321 fn = os.path.join(sourcedir, "cmdline")
322 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700323 cmd.append("--cmdline")
324 cmd.append(open(fn).read().rstrip("\n"))
325
326 fn = os.path.join(sourcedir, "base")
327 if os.access(fn, os.F_OK):
328 cmd.append("--base")
329 cmd.append(open(fn).read().rstrip("\n"))
330
Ying Wang4de6b5b2010-08-25 14:29:34 -0700331 fn = os.path.join(sourcedir, "pagesize")
332 if os.access(fn, os.F_OK):
333 cmd.append("--pagesize")
334 cmd.append(open(fn).read().rstrip("\n"))
335
Doug Zongkerd5131602012-08-02 14:46:42 -0700336 args = info_dict.get("mkbootimg_args", None)
337 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700338 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700339
Doug Zongker38a649f2009-06-17 09:07:09 -0700340 cmd.extend(["--ramdisk", ramdisk_img.name,
341 "--output", img.name])
342
343 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700344 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700345 assert p.returncode == 0, "mkbootimg of %s image failed" % (
346 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700347
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700348 if info_dict.get("verity_key", None):
349 path = "/" + os.path.basename(sourcedir).lower()
Sami Tolvanen8d212ea2014-11-06 20:38:00 -0800350 cmd = ["boot_signer", path, img.name, info_dict["verity_key"] + ".pk8", info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700351 p = Run(cmd, stdout=subprocess.PIPE)
352 p.communicate()
353 assert p.returncode == 0, "boot_signer of %s image failed" % path
354
Doug Zongkereef39442009-04-02 12:14:19 -0700355 img.seek(os.SEEK_SET, 0)
356 data = img.read()
357
358 ramdisk_img.close()
359 img.close()
360
361 return data
362
363
Doug Zongkerd5131602012-08-02 14:46:42 -0700364def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
365 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800366 """Return a File object (with name 'name') with the desired bootable
367 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700368 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
369 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800370 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700371
Doug Zongker55d93282011-01-25 17:03:34 -0800372 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
373 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700374 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800375 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700376
377 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
378 if os.path.exists(prebuilt_path):
379 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
380 return File.FromLocalFile(name, prebuilt_path)
381
382 print "building image from target_files %s..." % (tree_subdir,)
383 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
384 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
385 os.path.join(unpack_dir, fs_config),
386 info_dict)
387 if data:
388 return File(name, data)
389 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800390
Doug Zongkereef39442009-04-02 12:14:19 -0700391
Doug Zongker75f17362009-12-08 13:46:44 -0800392def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800393 """Unzip the given archive into a temporary directory and return the name.
394
395 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
396 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
397
398 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
399 main file), open for reading.
400 """
Doug Zongkereef39442009-04-02 12:14:19 -0700401
402 tmp = tempfile.mkdtemp(prefix="targetfiles-")
403 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800404
405 def unzip_to_dir(filename, dirname):
406 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
407 if pattern is not None:
408 cmd.append(pattern)
409 p = Run(cmd, stdout=subprocess.PIPE)
410 p.communicate()
411 if p.returncode != 0:
412 raise ExternalError("failed to unzip input target-files \"%s\"" %
413 (filename,))
414
415 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
416 if m:
417 unzip_to_dir(m.group(1), tmp)
418 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
419 filename = m.group(1)
420 else:
421 unzip_to_dir(filename, tmp)
422
423 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700424
425
426def GetKeyPasswords(keylist):
427 """Given a list of keys, prompt the user to enter passwords for
428 those which require them. Return a {key: password} dict. password
429 will be None if the key has no password."""
430
Doug Zongker8ce7c252009-05-22 13:34:54 -0700431 no_passwords = []
432 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700433 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700434 devnull = open("/dev/null", "w+b")
435 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800436 # We don't need a password for things that aren't really keys.
437 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700438 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700439 continue
440
T.R. Fullhart37e10522013-03-18 10:31:26 -0700441 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700442 "-inform", "DER", "-nocrypt"],
443 stdin=devnull.fileno(),
444 stdout=devnull.fileno(),
445 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700446 p.communicate()
447 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700448 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700449 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700450 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700451 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
452 "-inform", "DER", "-passin", "pass:"],
453 stdin=devnull.fileno(),
454 stdout=devnull.fileno(),
455 stderr=subprocess.PIPE)
456 stdout, stderr = p.communicate()
457 if p.returncode == 0:
458 # Encrypted key with empty string as password.
459 key_passwords[k] = ''
460 elif stderr.startswith('Error decrypting key'):
461 # Definitely encrypted key.
462 # It would have said "Error reading key" if it didn't parse correctly.
463 need_passwords.append(k)
464 else:
465 # Potentially, a type of key that openssl doesn't understand.
466 # We'll let the routines in signapk.jar handle it.
467 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700468 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700469
T.R. Fullhart37e10522013-03-18 10:31:26 -0700470 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700471 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700472 return key_passwords
473
474
Doug Zongker951495f2009-08-14 12:44:19 -0700475def SignFile(input_name, output_name, key, password, align=None,
476 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700477 """Sign the input_name zip/jar/apk, producing output_name. Use the
478 given key and password (the latter may be None if the key does not
479 have a password.
480
481 If align is an integer > 1, zipalign is run to align stored files in
482 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700483
484 If whole_file is true, use the "-w" option to SignApk to embed a
485 signature that covers the whole file in the archive comment of the
486 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700487 """
Doug Zongker951495f2009-08-14 12:44:19 -0700488
Doug Zongkereef39442009-04-02 12:14:19 -0700489 if align == 0 or align == 1:
490 align = None
491
492 if align:
493 temp = tempfile.NamedTemporaryFile()
494 sign_name = temp.name
495 else:
496 sign_name = output_name
497
Baligh Uddin339ee492014-09-05 11:18:07 -0700498 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700499 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
500 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700501 if whole_file:
502 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700503 cmd.extend([key + OPTIONS.public_key_suffix,
504 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700505 input_name, sign_name])
506
507 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700508 if password is not None:
509 password += "\n"
510 p.communicate(password)
511 if p.returncode != 0:
512 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
513
514 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700515 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700516 p.communicate()
517 if p.returncode != 0:
518 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
519 temp.close()
520
521
Doug Zongker37974732010-09-16 17:44:38 -0700522def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700523 """Check the data string passed against the max size limit, if
524 any, for the given target. Raise exception if the data is too big.
525 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700526
Doug Zongker1684d9c2010-09-17 07:44:38 -0700527 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700528 mount_point = "/" + target
529
Ying Wangf8824af2014-06-03 14:07:27 -0700530 fs_type = None
531 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700532 if info_dict["fstab"]:
533 if mount_point == "/userdata": mount_point = "/data"
534 p = info_dict["fstab"][mount_point]
535 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800536 device = p.device
537 if "/" in device:
538 device = device[device.rfind("/")+1:]
539 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700540 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700541
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700542 if fs_type == "yaffs2":
543 # image size should be increased by 1/64th to account for the
544 # spare area (64 bytes per 2k page)
545 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800546 size = len(data)
547 pct = float(size) * 100.0 / limit
548 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
549 if pct >= 99.0:
550 raise ExternalError(msg)
551 elif pct >= 95.0:
552 print
553 print " WARNING: ", msg
554 print
555 elif OPTIONS.verbose:
556 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700557
558
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800559def ReadApkCerts(tf_zip):
560 """Given a target_files ZipFile, parse the META/apkcerts.txt file
561 and return a {package: cert} dict."""
562 certmap = {}
563 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
564 line = line.strip()
565 if not line: continue
566 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
567 r'private_key="(.*)"$', line)
568 if m:
569 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700570 public_key_suffix_len = len(OPTIONS.public_key_suffix)
571 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800572 if cert in SPECIAL_CERT_STRINGS and not privkey:
573 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700574 elif (cert.endswith(OPTIONS.public_key_suffix) and
575 privkey.endswith(OPTIONS.private_key_suffix) and
576 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
577 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800578 else:
579 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
580 return certmap
581
582
Doug Zongkereef39442009-04-02 12:14:19 -0700583COMMON_DOCSTRING = """
584 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700585 Prepend <dir>/bin to the list of places to search for binaries
586 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700587
Doug Zongker05d3dea2009-06-22 11:32:31 -0700588 -s (--device_specific) <file>
589 Path to the python module containing device-specific
590 releasetools code.
591
Doug Zongker8bec09e2009-11-30 15:37:14 -0800592 -x (--extra) <key=value>
593 Add a key/value pair to the 'extras' dict, which device-specific
594 extension code may look at.
595
Doug Zongkereef39442009-04-02 12:14:19 -0700596 -v (--verbose)
597 Show command lines being executed.
598
599 -h (--help)
600 Display this usage message and exit.
601"""
602
603def Usage(docstring):
604 print docstring.rstrip("\n")
605 print COMMON_DOCSTRING
606
607
608def ParseOptions(argv,
609 docstring,
610 extra_opts="", extra_long_opts=(),
611 extra_option_handler=None):
612 """Parse the options in argv and return any arguments that aren't
613 flags. docstring is the calling module's docstring, to be displayed
614 for errors and -h. extra_opts and extra_long_opts are for flags
615 defined by the caller, which are processed by passing them to
616 extra_option_handler."""
617
618 try:
619 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800620 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700621 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700622 "java_path=", "java_args=", "public_key_suffix=",
623 "private_key_suffix=", "device_specific=", "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700625 except getopt.GetoptError, err:
626 Usage(docstring)
627 print "**", str(err), "**"
628 sys.exit(2)
629
630 path_specified = False
631
632 for o, a in opts:
633 if o in ("-h", "--help"):
634 Usage(docstring)
635 sys.exit()
636 elif o in ("-v", "--verbose"):
637 OPTIONS.verbose = True
638 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700639 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700640 elif o in ("--signapk_path",):
641 OPTIONS.signapk_path = a
642 elif o in ("--extra_signapk_args",):
643 OPTIONS.extra_signapk_args = shlex.split(a)
644 elif o in ("--java_path",):
645 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700646 elif o in ("--java_args",):
647 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700648 elif o in ("--public_key_suffix",):
649 OPTIONS.public_key_suffix = a
650 elif o in ("--private_key_suffix",):
651 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700652 elif o in ("-s", "--device_specific"):
653 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800654 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800655 key, value = a.split("=", 1)
656 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700657 else:
658 if extra_option_handler is None or not extra_option_handler(o, a):
659 assert False, "unknown option \"%s\"" % (o,)
660
Doug Zongker85448772014-09-09 14:59:20 -0700661 if OPTIONS.search_path:
662 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
663 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700664
665 return args
666
667
Doug Zongkerfc44a512014-08-26 13:10:25 -0700668def MakeTempFile(prefix=None, suffix=None):
669 """Make a temp file and add it to the list of things to be deleted
670 when Cleanup() is called. Return the filename."""
671 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
672 os.close(fd)
673 OPTIONS.tempfiles.append(fn)
674 return fn
675
676
Doug Zongkereef39442009-04-02 12:14:19 -0700677def Cleanup():
678 for i in OPTIONS.tempfiles:
679 if os.path.isdir(i):
680 shutil.rmtree(i)
681 else:
682 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700683
684
685class PasswordManager(object):
686 def __init__(self):
687 self.editor = os.getenv("EDITOR", None)
688 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
689
690 def GetPasswords(self, items):
691 """Get passwords corresponding to each string in 'items',
692 returning a dict. (The dict may have keys in addition to the
693 values in 'items'.)
694
695 Uses the passwords in $ANDROID_PW_FILE if available, letting the
696 user edit that file to add more needed passwords. If no editor is
697 available, or $ANDROID_PW_FILE isn't define, prompts the user
698 interactively in the ordinary way.
699 """
700
701 current = self.ReadFile()
702
703 first = True
704 while True:
705 missing = []
706 for i in items:
707 if i not in current or not current[i]:
708 missing.append(i)
709 # Are all the passwords already in the file?
710 if not missing: return current
711
712 for i in missing:
713 current[i] = ""
714
715 if not first:
716 print "key file %s still missing some passwords." % (self.pwfile,)
717 answer = raw_input("try to edit again? [y]> ").strip()
718 if answer and answer[0] not in 'yY':
719 raise RuntimeError("key passwords unavailable")
720 first = False
721
722 current = self.UpdateAndReadFile(current)
723
724 def PromptResult(self, current):
725 """Prompt the user to enter a value (password) for each key in
726 'current' whose value is fales. Returns a new dict with all the
727 values.
728 """
729 result = {}
730 for k, v in sorted(current.iteritems()):
731 if v:
732 result[k] = v
733 else:
734 while True:
735 result[k] = getpass.getpass("Enter password for %s key> "
736 % (k,)).strip()
737 if result[k]: break
738 return result
739
740 def UpdateAndReadFile(self, current):
741 if not self.editor or not self.pwfile:
742 return self.PromptResult(current)
743
744 f = open(self.pwfile, "w")
745 os.chmod(self.pwfile, 0600)
746 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
747 f.write("# (Additional spaces are harmless.)\n\n")
748
749 first_line = None
750 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
751 sorted.sort()
752 for i, (_, k, v) in enumerate(sorted):
753 f.write("[[[ %s ]]] %s\n" % (v, k))
754 if not v and first_line is None:
755 # position cursor on first line with no password.
756 first_line = i + 4
757 f.close()
758
759 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
760 _, _ = p.communicate()
761
762 return self.ReadFile()
763
764 def ReadFile(self):
765 result = {}
766 if self.pwfile is None: return result
767 try:
768 f = open(self.pwfile, "r")
769 for line in f:
770 line = line.strip()
771 if not line or line[0] == '#': continue
772 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
773 if not m:
774 print "failed to parse password file: ", line
775 else:
776 result[m.group(2)] = m.group(1)
777 f.close()
778 except IOError, e:
779 if e.errno != errno.ENOENT:
780 print "error reading password file: ", str(e)
781 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700782
783
Dan Albertcd082d42015-01-27 15:53:15 -0800784def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
785 compress_type=None):
786 import datetime
787
788 # http://b/18015246
789 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
790 # for files larger than 2GiB. We can work around this by adjusting their
791 # limit. Note that `zipfile.writestr()` will not work for strings larger than
792 # 2GiB. The Python interpreter sometimes rejects strings that large (though
793 # it isn't clear to me exactly what circumstances cause this).
794 # `zipfile.write()` must be used directly to work around this.
795 #
796 # This mess can be avoided if we port to python3.
797 saved_zip64_limit = zipfile.ZIP64_LIMIT
798 zipfile.ZIP64_LIMIT = (1 << 32) - 1
799
800 compress_type = compress_type or zip_file.compression
801 arcname = arcname or filename
802
803 saved_stat = os.stat(filename)
804
805 try:
806 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
807 # file to be zipped and reset it when we're done.
808 os.chmod(filename, perms)
809
810 # Use a fixed timestamp so the output is repeatable.
811 epoch = datetime.datetime.fromtimestamp(0)
812 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
813 os.utime(filename, (timestamp, timestamp))
814
815 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
816 finally:
817 os.chmod(filename, saved_stat.st_mode)
818 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
819 zipfile.ZIP64_LIMIT = saved_zip64_limit
820
821
Geremy Condra36bd3652014-02-06 19:45:10 -0800822def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700823 # use a fixed timestamp so the output is repeatable.
824 zinfo = zipfile.ZipInfo(filename=filename,
825 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800826 if compression is None:
827 zinfo.compress_type = zip.compression
828 else:
829 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700830 zinfo.external_attr = perms << 16
831 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700832
833
834class DeviceSpecificParams(object):
835 module = None
836 def __init__(self, **kwargs):
837 """Keyword arguments to the constructor become attributes of this
838 object, which is passed to all functions in the device-specific
839 module."""
840 for k, v in kwargs.iteritems():
841 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800842 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700843
844 if self.module is None:
845 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700846 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700847 try:
848 if os.path.isdir(path):
849 info = imp.find_module("releasetools", [path])
850 else:
851 d, f = os.path.split(path)
852 b, x = os.path.splitext(f)
853 if x == ".py":
854 f = b
855 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800856 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700857 self.module = imp.load_module("device_specific", *info)
858 except ImportError:
859 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700860
861 def _DoCall(self, function_name, *args, **kwargs):
862 """Call the named function in the device-specific module, passing
863 the given args and kwargs. The first argument to the call will be
864 the DeviceSpecific object itself. If there is no module, or the
865 module does not define the function, return the value of the
866 'default' kwarg (which itself defaults to None)."""
867 if self.module is None or not hasattr(self.module, function_name):
868 return kwargs.get("default", None)
869 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
870
871 def FullOTA_Assertions(self):
872 """Called after emitting the block of assertions at the top of a
873 full OTA package. Implementations can add whatever additional
874 assertions they like."""
875 return self._DoCall("FullOTA_Assertions")
876
Doug Zongkere5ff5902012-01-17 10:55:37 -0800877 def FullOTA_InstallBegin(self):
878 """Called at the start of full OTA installation."""
879 return self._DoCall("FullOTA_InstallBegin")
880
Doug Zongker05d3dea2009-06-22 11:32:31 -0700881 def FullOTA_InstallEnd(self):
882 """Called at the end of full OTA installation; typically this is
883 used to install the image for the device's baseband processor."""
884 return self._DoCall("FullOTA_InstallEnd")
885
886 def IncrementalOTA_Assertions(self):
887 """Called after emitting the block of assertions at the top of an
888 incremental OTA package. Implementations can add whatever
889 additional assertions they like."""
890 return self._DoCall("IncrementalOTA_Assertions")
891
Doug Zongkere5ff5902012-01-17 10:55:37 -0800892 def IncrementalOTA_VerifyBegin(self):
893 """Called at the start of the verification phase of incremental
894 OTA installation; additional checks can be placed here to abort
895 the script before any changes are made."""
896 return self._DoCall("IncrementalOTA_VerifyBegin")
897
Doug Zongker05d3dea2009-06-22 11:32:31 -0700898 def IncrementalOTA_VerifyEnd(self):
899 """Called at the end of the verification phase of incremental OTA
900 installation; additional checks can be placed here to abort the
901 script before any changes are made."""
902 return self._DoCall("IncrementalOTA_VerifyEnd")
903
Doug Zongkere5ff5902012-01-17 10:55:37 -0800904 def IncrementalOTA_InstallBegin(self):
905 """Called at the start of incremental OTA installation (after
906 verification is complete)."""
907 return self._DoCall("IncrementalOTA_InstallBegin")
908
Doug Zongker05d3dea2009-06-22 11:32:31 -0700909 def IncrementalOTA_InstallEnd(self):
910 """Called at the end of incremental OTA installation; typically
911 this is used to install the image for the device's baseband
912 processor."""
913 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700914
915class File(object):
916 def __init__(self, name, data):
917 self.name = name
918 self.data = data
919 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800920 self.sha1 = sha1(data).hexdigest()
921
922 @classmethod
923 def FromLocalFile(cls, name, diskname):
924 f = open(diskname, "rb")
925 data = f.read()
926 f.close()
927 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700928
929 def WriteToTemp(self):
930 t = tempfile.NamedTemporaryFile()
931 t.write(self.data)
932 t.flush()
933 return t
934
Geremy Condra36bd3652014-02-06 19:45:10 -0800935 def AddToZip(self, z, compression=None):
936 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700937
938DIFF_PROGRAM_BY_EXT = {
939 ".gz" : "imgdiff",
940 ".zip" : ["imgdiff", "-z"],
941 ".jar" : ["imgdiff", "-z"],
942 ".apk" : ["imgdiff", "-z"],
943 ".img" : "imgdiff",
944 }
945
946class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700947 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700948 self.tf = tf
949 self.sf = sf
950 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700951 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700952
953 def ComputePatch(self):
954 """Compute the patch (as a string of data) needed to turn sf into
955 tf. Returns the same tuple as GetPatch()."""
956
957 tf = self.tf
958 sf = self.sf
959
Doug Zongker24cd2802012-08-14 16:36:15 -0700960 if self.diff_program:
961 diff_program = self.diff_program
962 else:
963 ext = os.path.splitext(tf.name)[1]
964 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700965
966 ttemp = tf.WriteToTemp()
967 stemp = sf.WriteToTemp()
968
969 ext = os.path.splitext(tf.name)[1]
970
971 try:
972 ptemp = tempfile.NamedTemporaryFile()
973 if isinstance(diff_program, list):
974 cmd = copy.copy(diff_program)
975 else:
976 cmd = [diff_program]
977 cmd.append(stemp.name)
978 cmd.append(ttemp.name)
979 cmd.append(ptemp.name)
980 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700981 err = []
982 def run():
983 _, e = p.communicate()
984 if e: err.append(e)
985 th = threading.Thread(target=run)
986 th.start()
987 th.join(timeout=300) # 5 mins
988 if th.is_alive():
989 print "WARNING: diff command timed out"
990 p.terminate()
991 th.join(5)
992 if th.is_alive():
993 p.kill()
994 th.join()
995
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700996 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700997 print "WARNING: failure running %s:\n%s\n" % (
998 diff_program, "".join(err))
999 self.patch = None
1000 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001001 diff = ptemp.read()
1002 finally:
1003 ptemp.close()
1004 stemp.close()
1005 ttemp.close()
1006
1007 self.patch = diff
1008 return self.tf, self.sf, self.patch
1009
1010
1011 def GetPatch(self):
1012 """Return a tuple (target_file, source_file, patch_data).
1013 patch_data may be None if ComputePatch hasn't been called, or if
1014 computing the patch failed."""
1015 return self.tf, self.sf, self.patch
1016
1017
1018def ComputeDifferences(diffs):
1019 """Call ComputePatch on all the Difference objects in 'diffs'."""
1020 print len(diffs), "diffs to compute"
1021
1022 # Do the largest files first, to try and reduce the long-pole effect.
1023 by_size = [(i.tf.size, i) for i in diffs]
1024 by_size.sort(reverse=True)
1025 by_size = [i[1] for i in by_size]
1026
1027 lock = threading.Lock()
1028 diff_iter = iter(by_size) # accessed under lock
1029
1030 def worker():
1031 try:
1032 lock.acquire()
1033 for d in diff_iter:
1034 lock.release()
1035 start = time.time()
1036 d.ComputePatch()
1037 dur = time.time() - start
1038 lock.acquire()
1039
1040 tf, sf, patch = d.GetPatch()
1041 if sf.name == tf.name:
1042 name = tf.name
1043 else:
1044 name = "%s (%s)" % (tf.name, sf.name)
1045 if patch is None:
1046 print "patching failed! %s" % (name,)
1047 else:
1048 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1049 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1050 lock.release()
1051 except Exception, e:
1052 print e
1053 raise
1054
1055 # start worker threads; wait for them all to finish.
1056 threads = [threading.Thread(target=worker)
1057 for i in range(OPTIONS.worker_threads)]
1058 for th in threads:
1059 th.start()
1060 while threads:
1061 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001062
1063
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001064class BlockDifference:
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001065 def __init__(self, partition, tgt, src=None, check_first_block=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001066 self.tgt = tgt
1067 self.src = src
1068 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001069 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001070
Doug Zongker62338182014-09-08 08:29:55 -07001071 version = 1
1072 if OPTIONS.info_dict:
1073 version = max(
1074 int(i) for i in
1075 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1076
1077 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
1078 version=version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001079 tmpdir = tempfile.mkdtemp()
1080 OPTIONS.tempfiles.append(tmpdir)
1081 self.path = os.path.join(tmpdir, partition)
1082 b.Compute(self.path)
1083
1084 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1085
1086 def WriteScript(self, script, output_zip, progress=None):
1087 if not self.src:
1088 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001089 script.Print("Patching %s image unconditionally..." % (self.partition,))
1090 else:
1091 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001092
Jesse Zhao75bcea02015-01-06 10:59:53 -08001093 if progress: script.ShowProgress(progress, 0)
1094 self._WriteUpdate(script, output_zip)
1095
1096 def WriteVerifyScript(self, script):
Sami Tolvanencac671a2014-12-09 16:40:34 +00001097 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001098 if not self.src:
Sami Tolvanencac671a2014-12-09 16:40:34 +00001099 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001100 else:
Sami Tolvanencac671a2014-12-09 16:40:34 +00001101 script.AppendExtra(('if block_image_verify("%s", '
1102 'package_extract_file("%s.transfer.list"), '
1103 '"%s.new.dat", "%s.patch.dat") then') %
1104 (self.device, partition, partition, partition))
1105 script.Print("Verified %s image..." % (partition,))
1106 script.AppendExtra('else');
1107
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001108 if self.check_first_block:
1109 self._CheckFirstBlock(script)
1110
Sami Tolvanencac671a2014-12-09 16:40:34 +00001111 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") ||\n'
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001112 ' abort("%s partition has unexpected contents");\n'
1113 'endif;') %
1114 (self.device, self.tgt.care_map.to_string_raw(),
1115 self.tgt.TotalSha1(), self.partition))
1116
1117 def _WriteUpdate(self, script, output_zip):
Dan Albertcd082d42015-01-27 15:53:15 -08001118 ZipWrite(output_zip,
1119 '{}.transfer.list'.format(self.path),
1120 '{}.transfer.list'.format(self.partition))
1121 ZipWrite(output_zip,
1122 '{}.new.dat'.format(self.path),
1123 '{}.new.dat'.format(self.partition))
1124 ZipWrite(output_zip,
1125 '{}.patch.dat'.format(self.path),
1126 '{}.patch.dat'.format(self.partition),
1127 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001128
Dan Albertcd082d42015-01-27 15:53:15 -08001129 call = ('block_image_update("{device}", '
1130 'package_extract_file("{partition}.transfer.list"), '
1131 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1132 device=self.device, partition=self.partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001133 script.AppendExtra(script._WordWrap(call))
1134
Sami Tolvanencac671a2014-12-09 16:40:34 +00001135 def _HashBlocks(self, source, ranges):
1136 data = source.ReadRangeSet(ranges)
1137 ctx = sha1()
1138
1139 for p in data:
1140 ctx.update(p)
1141
1142 return ctx.hexdigest()
1143
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001144 def _CheckFirstBlock(self, script):
1145 r = RangeSet((0, 1))
Sami Tolvanencac671a2014-12-09 16:40:34 +00001146 srchash = self._HashBlocks(self.src, r);
1147 tgthash = self._HashBlocks(self.tgt, r);
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001148
1149 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
Sami Tolvanencac671a2014-12-09 16:40:34 +00001150 '(range_sha1("%s", "%s") == "%s") || '
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001151 'abort("%s has been remounted R/W; '
1152 'reflash device to reenable OTA updates");')
Sami Tolvanencac671a2014-12-09 16:40:34 +00001153 % (self.device, r.to_string_raw(), srchash,
1154 self.device, r.to_string_raw(), tgthash,
1155 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001156
1157DataImage = blockimgdiff.DataImage
1158
1159
Doug Zongker96a57e72010-09-26 14:57:41 -07001160# map recovery.fstab's fs_types to mount/format "partition types"
1161PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001162 "ext4": "EMMC", "emmc": "EMMC",
1163 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001164
1165def GetTypeAndDevice(mount_point, info):
1166 fstab = info["fstab"]
1167 if fstab:
1168 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1169 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001170 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001171
1172
1173def ParseCertificate(data):
1174 """Parse a PEM-format certificate."""
1175 cert = []
1176 save = False
1177 for line in data.split("\n"):
1178 if "--END CERTIFICATE--" in line:
1179 break
1180 if save:
1181 cert.append(line)
1182 if "--BEGIN CERTIFICATE--" in line:
1183 save = True
1184 cert = "".join(cert).decode('base64')
1185 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001186
Doug Zongker412c02f2014-02-13 10:58:24 -08001187def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1188 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001189 """Generate a binary patch that creates the recovery image starting
1190 with the boot image. (Most of the space in these images is just the
1191 kernel, which is identical for the two, so the resulting patch
1192 should be efficient.) Add it to the output zip, along with a shell
1193 script that is run from init.rc on first boot to actually do the
1194 patching and install the new recovery image.
1195
1196 recovery_img and boot_img should be File objects for the
1197 corresponding images. info should be the dictionary returned by
1198 common.LoadInfoDict() on the input target_files.
1199 """
1200
Doug Zongker412c02f2014-02-13 10:58:24 -08001201 if info_dict is None:
1202 info_dict = OPTIONS.info_dict
1203
Doug Zongkerc9253822014-02-04 12:17:58 -08001204 diff_program = ["imgdiff"]
1205 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1206 if os.path.exists(path):
1207 diff_program.append("-b")
1208 diff_program.append(path)
1209 bonus_args = "-b /system/etc/recovery-resource.dat"
1210 else:
1211 bonus_args = ""
1212
1213 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1214 _, _, patch = d.ComputePatch()
1215 output_sink("recovery-from-boot.p", patch)
1216
Ying Wanga961a092014-07-29 11:42:37 -07001217 td_pair = GetTypeAndDevice("/boot", info_dict)
1218 if not td_pair:
1219 return
1220 boot_type, boot_device = td_pair
1221 td_pair = GetTypeAndDevice("/recovery", info_dict)
1222 if not td_pair:
1223 return
1224 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001225
1226 sh = """#!/system/bin/sh
1227if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1228 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"
1229else
1230 log -t recovery "Recovery image already installed"
1231fi
1232""" % { 'boot_size': boot_img.size,
1233 'boot_sha1': boot_img.sha1,
1234 'recovery_size': recovery_img.size,
1235 'recovery_sha1': recovery_img.sha1,
1236 'boot_type': boot_type,
1237 'boot_device': boot_device,
1238 'recovery_type': recovery_type,
1239 'recovery_device': recovery_device,
1240 'bonus_args': bonus_args,
1241 }
1242
1243 # The install script location moved from /system/etc to /system/bin
1244 # in the L release. Parse the init.rc file to find out where the
1245 # target-files expects it to be, and put it there.
1246 sh_location = "etc/install-recovery.sh"
1247 try:
1248 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1249 for line in f:
1250 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1251 if m:
1252 sh_location = m.group(1)
1253 print "putting script in", sh_location
1254 break
1255 except (OSError, IOError), e:
1256 print "failed to read init.rc: %s" % (e,)
1257
1258 output_sink(sh_location, sh)