blob: 4fe4938316dbda1612a9aeecced6f78992764652 [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 Zongker55d93282011-01-25 17:03:34 -080032try:
davidcad0bb92011-03-15 14:21:38 +000033 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080034except ImportError:
davidcad0bb92011-03-15 14:21:38 +000035 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037# missing in Python 2.4 and before
38if not hasattr(os, "SEEK_SET"):
39 os.SEEK_SET = 0
40
41class Options(object): pass
42OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070043OPTIONS.search_path = "out/host/linux-x86"
T.R. Fullhart37e10522013-03-18 10:31:26 -070044OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
45OPTIONS.extra_signapk_args = []
46OPTIONS.java_path = "java" # Use the one on the path by default.
47OPTIONS.public_key_suffix = ".x509.pem"
48OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070049OPTIONS.verbose = False
50OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070051OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080052OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070053OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070054
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080055
56# Values for "certificate" in apkcerts that mean special things.
57SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
58
59
Doug Zongkereef39442009-04-02 12:14:19 -070060class ExternalError(RuntimeError): pass
61
62
63def Run(args, **kwargs):
64 """Create and return a subprocess.Popen object, printing the command
65 line on the terminal if -v was specified."""
66 if OPTIONS.verbose:
67 print " running: ", " ".join(args)
68 return subprocess.Popen(args, **kwargs)
69
70
Ying Wang7e6d4e42010-12-13 16:25:36 -080071def CloseInheritedPipes():
72 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
73 before doing other work."""
74 if platform.system() != "Darwin":
75 return
76 for d in range(3, 1025):
77 try:
78 stat = os.fstat(d)
79 if stat is not None:
80 pipebit = stat[0] & 0x1000
81 if pipebit != 0:
82 os.close(d)
83 except OSError:
84 pass
85
86
Doug Zongkerc9253822014-02-04 12:17:58 -080087def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070088 """Read and parse the META/misc_info.txt key/value pairs from the
89 input target files and return a dict."""
90
Doug Zongkerc9253822014-02-04 12:17:58 -080091 def read_helper(fn):
92 if isinstance(input, zipfile.ZipFile):
93 return input.read(fn)
94 else:
95 path = os.path.join(input, *fn.split("/"))
96 try:
97 with open(path) as f:
98 return f.read()
99 except IOError, e:
100 if e.errno == errno.ENOENT:
101 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700102 d = {}
103 try:
Michael Runge6e836112014-04-15 17:40:21 -0700104 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700105 except KeyError:
106 # ok if misc_info.txt doesn't exist
107 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700108
Doug Zongker37974732010-09-16 17:44:38 -0700109 # backwards compatibility: These values used to be in their own
110 # files. Look for them, in case we're processing an old
111 # target_files zip.
112
113 if "mkyaffs2_extra_flags" not in d:
114 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800115 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700116 except KeyError:
117 # ok if flags don't exist
118 pass
119
120 if "recovery_api_version" not in d:
121 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800122 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700123 except KeyError:
124 raise ValueError("can't find recovery API version in input target-files")
125
126 if "tool_extensions" not in d:
127 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800128 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700129 except KeyError:
130 # ok if extensions don't exist
131 pass
132
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800133 if "fstab_version" not in d:
134 d["fstab_version"] = "1"
135
Doug Zongker37974732010-09-16 17:44:38 -0700136 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800137 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700138 for line in data.split("\n"):
139 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700140 name, value = line.split(" ", 1)
141 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700142 if name == "blocksize":
143 d[name] = value
144 else:
145 d[name + "_size"] = value
146 except KeyError:
147 pass
148
149 def makeint(key):
150 if key in d:
151 d[key] = int(d[key], 0)
152
153 makeint("recovery_api_version")
154 makeint("blocksize")
155 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700156 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700157 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700158 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700159 makeint("recovery_size")
160 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800161 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700162
Doug Zongkerc9253822014-02-04 12:17:58 -0800163 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
164 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700165 return d
166
Doug Zongkerc9253822014-02-04 12:17:58 -0800167def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700168 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800169 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700170 except KeyError:
171 print "Warning: could not find SYSTEM/build.prop in %s" % zip
172 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700173 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700174
Michael Runge6e836112014-04-15 17:40:21 -0700175def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700176 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700177 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700178 line = line.strip()
179 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700180 if "=" in line:
181 name, value = line.split("=", 1)
182 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700183 return d
184
Doug Zongkerc9253822014-02-04 12:17:58 -0800185def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700186 class Partition(object):
187 pass
188
189 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800190 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700191 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800192 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700193 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700194
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800195 if fstab_version == 1:
196 d = {}
197 for line in data.split("\n"):
198 line = line.strip()
199 if not line or line.startswith("#"): continue
200 pieces = line.split()
201 if not (3 <= len(pieces) <= 4):
202 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700203
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800204 p = Partition()
205 p.mount_point = pieces[0]
206 p.fs_type = pieces[1]
207 p.device = pieces[2]
208 p.length = 0
209 options = None
210 if len(pieces) >= 4:
211 if pieces[3].startswith("/"):
212 p.device2 = pieces[3]
213 if len(pieces) >= 5:
214 options = pieces[4]
215 else:
216 p.device2 = None
217 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800218 else:
219 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700220
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800221 if options:
222 options = options.split(",")
223 for i in options:
224 if i.startswith("length="):
225 p.length = int(i[7:])
226 else:
227 print "%s: unknown option \"%s\"" % (p.mount_point, i)
228
229 d[p.mount_point] = p
230
231 elif fstab_version == 2:
232 d = {}
233 for line in data.split("\n"):
234 line = line.strip()
235 if not line or line.startswith("#"): continue
236 pieces = line.split()
237 if len(pieces) != 5:
238 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
239
240 # Ignore entries that are managed by vold
241 options = pieces[4]
242 if "voldmanaged=" in options: continue
243
244 # It's a good line, parse it
245 p = Partition()
246 p.device = pieces[0]
247 p.mount_point = pieces[1]
248 p.fs_type = pieces[2]
249 p.device2 = None
250 p.length = 0
251
Doug Zongker086cbb02011-02-17 15:54:20 -0800252 options = options.split(",")
253 for i in options:
254 if i.startswith("length="):
255 p.length = int(i[7:])
256 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 # Ignore all unknown options in the unified fstab
258 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800259
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800260 d[p.mount_point] = p
261
262 else:
263 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
264
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700265 return d
266
267
Doug Zongker37974732010-09-16 17:44:38 -0700268def DumpInfoDict(d):
269 for k, v in sorted(d.items()):
270 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700271
Doug Zongkerd5131602012-08-02 14:46:42 -0700272def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700273 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700274 'sourcedir'), and turn them into a boot image. Return the image
275 data, or None if sourcedir does not appear to contains files for
276 building the requested image."""
277
278 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
279 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
280 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700281
Doug Zongkerd5131602012-08-02 14:46:42 -0700282 if info_dict is None:
283 info_dict = OPTIONS.info_dict
284
Doug Zongkereef39442009-04-02 12:14:19 -0700285 ramdisk_img = tempfile.NamedTemporaryFile()
286 img = tempfile.NamedTemporaryFile()
287
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700288 if os.access(fs_config_file, os.F_OK):
289 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
290 else:
291 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
292 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700293 p2 = Run(["minigzip"],
294 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700295
296 p2.wait()
297 p1.wait()
298 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700299 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700300
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800301 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
302 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
303
304 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700305
Benoit Fradina45a8682014-07-14 21:00:43 +0200306 fn = os.path.join(sourcedir, "second")
307 if os.access(fn, os.F_OK):
308 cmd.append("--second")
309 cmd.append(fn)
310
Doug Zongker171f1cd2009-06-15 22:36:37 -0700311 fn = os.path.join(sourcedir, "cmdline")
312 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700313 cmd.append("--cmdline")
314 cmd.append(open(fn).read().rstrip("\n"))
315
316 fn = os.path.join(sourcedir, "base")
317 if os.access(fn, os.F_OK):
318 cmd.append("--base")
319 cmd.append(open(fn).read().rstrip("\n"))
320
Ying Wang4de6b5b2010-08-25 14:29:34 -0700321 fn = os.path.join(sourcedir, "pagesize")
322 if os.access(fn, os.F_OK):
323 cmd.append("--pagesize")
324 cmd.append(open(fn).read().rstrip("\n"))
325
Doug Zongkerd5131602012-08-02 14:46:42 -0700326 args = info_dict.get("mkbootimg_args", None)
327 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700328 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700329
Doug Zongker38a649f2009-06-17 09:07:09 -0700330 cmd.extend(["--ramdisk", ramdisk_img.name,
331 "--output", img.name])
332
333 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700334 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700335 assert p.returncode == 0, "mkbootimg of %s image failed" % (
336 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700337
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700338 if info_dict.get("verity_key", None):
339 path = "/" + os.path.basename(sourcedir).lower()
340 cmd = ["boot_signer", path, img.name, info_dict["verity_key"], img.name]
341 p = Run(cmd, stdout=subprocess.PIPE)
342 p.communicate()
343 assert p.returncode == 0, "boot_signer of %s image failed" % path
344
Doug Zongkereef39442009-04-02 12:14:19 -0700345 img.seek(os.SEEK_SET, 0)
346 data = img.read()
347
348 ramdisk_img.close()
349 img.close()
350
351 return data
352
353
Doug Zongkerd5131602012-08-02 14:46:42 -0700354def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
355 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800356 """Return a File object (with name 'name') with the desired bootable
357 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
358 'prebuilt_name', otherwise construct it from the source files in
359 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700360
Doug Zongker55d93282011-01-25 17:03:34 -0800361 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
362 if os.path.exists(prebuilt_path):
363 print "using prebuilt %s..." % (prebuilt_name,)
364 return File.FromLocalFile(name, prebuilt_path)
365 else:
366 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700367 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Ying Wangd89ffa82014-02-05 11:28:51 -0800368 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
369 os.path.join(unpack_dir, fs_config),
Ying Wang64a55ba2014-02-05 12:15:06 -0800370 info_dict)
Ying Wangd89ffa82014-02-05 11:28:51 -0800371 if data:
372 return File(name, data)
373 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800374
Doug Zongkereef39442009-04-02 12:14:19 -0700375
Doug Zongker75f17362009-12-08 13:46:44 -0800376def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800377 """Unzip the given archive into a temporary directory and return the name.
378
379 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
380 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
381
382 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
383 main file), open for reading.
384 """
Doug Zongkereef39442009-04-02 12:14:19 -0700385
386 tmp = tempfile.mkdtemp(prefix="targetfiles-")
387 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800388
389 def unzip_to_dir(filename, dirname):
390 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
391 if pattern is not None:
392 cmd.append(pattern)
393 p = Run(cmd, stdout=subprocess.PIPE)
394 p.communicate()
395 if p.returncode != 0:
396 raise ExternalError("failed to unzip input target-files \"%s\"" %
397 (filename,))
398
399 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
400 if m:
401 unzip_to_dir(m.group(1), tmp)
402 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
403 filename = m.group(1)
404 else:
405 unzip_to_dir(filename, tmp)
406
407 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700408
409
410def GetKeyPasswords(keylist):
411 """Given a list of keys, prompt the user to enter passwords for
412 those which require them. Return a {key: password} dict. password
413 will be None if the key has no password."""
414
Doug Zongker8ce7c252009-05-22 13:34:54 -0700415 no_passwords = []
416 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700417 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700418 devnull = open("/dev/null", "w+b")
419 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800420 # We don't need a password for things that aren't really keys.
421 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700422 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700423 continue
424
T.R. Fullhart37e10522013-03-18 10:31:26 -0700425 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700426 "-inform", "DER", "-nocrypt"],
427 stdin=devnull.fileno(),
428 stdout=devnull.fileno(),
429 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700430 p.communicate()
431 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700432 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700433 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700434 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700435 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
436 "-inform", "DER", "-passin", "pass:"],
437 stdin=devnull.fileno(),
438 stdout=devnull.fileno(),
439 stderr=subprocess.PIPE)
440 stdout, stderr = p.communicate()
441 if p.returncode == 0:
442 # Encrypted key with empty string as password.
443 key_passwords[k] = ''
444 elif stderr.startswith('Error decrypting key'):
445 # Definitely encrypted key.
446 # It would have said "Error reading key" if it didn't parse correctly.
447 need_passwords.append(k)
448 else:
449 # Potentially, a type of key that openssl doesn't understand.
450 # We'll let the routines in signapk.jar handle it.
451 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700452 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700453
T.R. Fullhart37e10522013-03-18 10:31:26 -0700454 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700455 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700456 return key_passwords
457
458
Doug Zongker951495f2009-08-14 12:44:19 -0700459def SignFile(input_name, output_name, key, password, align=None,
460 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700461 """Sign the input_name zip/jar/apk, producing output_name. Use the
462 given key and password (the latter may be None if the key does not
463 have a password.
464
465 If align is an integer > 1, zipalign is run to align stored files in
466 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700467
468 If whole_file is true, use the "-w" option to SignApk to embed a
469 signature that covers the whole file in the archive comment of the
470 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700471 """
Doug Zongker951495f2009-08-14 12:44:19 -0700472
Doug Zongkereef39442009-04-02 12:14:19 -0700473 if align == 0 or align == 1:
474 align = None
475
476 if align:
477 temp = tempfile.NamedTemporaryFile()
478 sign_name = temp.name
479 else:
480 sign_name = output_name
481
T.R. Fullhart37e10522013-03-18 10:31:26 -0700482 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
483 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
484 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700485 if whole_file:
486 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700487 cmd.extend([key + OPTIONS.public_key_suffix,
488 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700489 input_name, sign_name])
490
491 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700492 if password is not None:
493 password += "\n"
494 p.communicate(password)
495 if p.returncode != 0:
496 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
497
498 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700499 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700500 p.communicate()
501 if p.returncode != 0:
502 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
503 temp.close()
504
505
Doug Zongker37974732010-09-16 17:44:38 -0700506def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700507 """Check the data string passed against the max size limit, if
508 any, for the given target. Raise exception if the data is too big.
509 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700510
Doug Zongker1684d9c2010-09-17 07:44:38 -0700511 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700512 mount_point = "/" + target
513
Ying Wangf8824af2014-06-03 14:07:27 -0700514 fs_type = None
515 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700516 if info_dict["fstab"]:
517 if mount_point == "/userdata": mount_point = "/data"
518 p = info_dict["fstab"][mount_point]
519 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800520 device = p.device
521 if "/" in device:
522 device = device[device.rfind("/")+1:]
523 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700524 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700525
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700526 if fs_type == "yaffs2":
527 # image size should be increased by 1/64th to account for the
528 # spare area (64 bytes per 2k page)
529 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800530 size = len(data)
531 pct = float(size) * 100.0 / limit
532 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
533 if pct >= 99.0:
534 raise ExternalError(msg)
535 elif pct >= 95.0:
536 print
537 print " WARNING: ", msg
538 print
539 elif OPTIONS.verbose:
540 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700541
542
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800543def ReadApkCerts(tf_zip):
544 """Given a target_files ZipFile, parse the META/apkcerts.txt file
545 and return a {package: cert} dict."""
546 certmap = {}
547 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
548 line = line.strip()
549 if not line: continue
550 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
551 r'private_key="(.*)"$', line)
552 if m:
553 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700554 public_key_suffix_len = len(OPTIONS.public_key_suffix)
555 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800556 if cert in SPECIAL_CERT_STRINGS and not privkey:
557 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700558 elif (cert.endswith(OPTIONS.public_key_suffix) and
559 privkey.endswith(OPTIONS.private_key_suffix) and
560 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
561 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800562 else:
563 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
564 return certmap
565
566
Doug Zongkereef39442009-04-02 12:14:19 -0700567COMMON_DOCSTRING = """
568 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700569 Prepend <dir>/bin to the list of places to search for binaries
570 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700571
Doug Zongker05d3dea2009-06-22 11:32:31 -0700572 -s (--device_specific) <file>
573 Path to the python module containing device-specific
574 releasetools code.
575
Doug Zongker8bec09e2009-11-30 15:37:14 -0800576 -x (--extra) <key=value>
577 Add a key/value pair to the 'extras' dict, which device-specific
578 extension code may look at.
579
Doug Zongkereef39442009-04-02 12:14:19 -0700580 -v (--verbose)
581 Show command lines being executed.
582
583 -h (--help)
584 Display this usage message and exit.
585"""
586
587def Usage(docstring):
588 print docstring.rstrip("\n")
589 print COMMON_DOCSTRING
590
591
592def ParseOptions(argv,
593 docstring,
594 extra_opts="", extra_long_opts=(),
595 extra_option_handler=None):
596 """Parse the options in argv and return any arguments that aren't
597 flags. docstring is the calling module's docstring, to be displayed
598 for errors and -h. extra_opts and extra_long_opts are for flags
599 defined by the caller, which are processed by passing them to
600 extra_option_handler."""
601
602 try:
603 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800604 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700605 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
606 "java_path=", "public_key_suffix=", "private_key_suffix=",
607 "device_specific=", "extra="] +
608 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700609 except getopt.GetoptError, err:
610 Usage(docstring)
611 print "**", str(err), "**"
612 sys.exit(2)
613
614 path_specified = False
615
616 for o, a in opts:
617 if o in ("-h", "--help"):
618 Usage(docstring)
619 sys.exit()
620 elif o in ("-v", "--verbose"):
621 OPTIONS.verbose = True
622 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700623 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 elif o in ("--signapk_path",):
625 OPTIONS.signapk_path = a
626 elif o in ("--extra_signapk_args",):
627 OPTIONS.extra_signapk_args = shlex.split(a)
628 elif o in ("--java_path",):
629 OPTIONS.java_path = a
630 elif o in ("--public_key_suffix",):
631 OPTIONS.public_key_suffix = a
632 elif o in ("--private_key_suffix",):
633 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700634 elif o in ("-s", "--device_specific"):
635 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800636 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800637 key, value = a.split("=", 1)
638 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700639 else:
640 if extra_option_handler is None or not extra_option_handler(o, a):
641 assert False, "unknown option \"%s\"" % (o,)
642
Doug Zongker602a84e2009-06-18 08:35:12 -0700643 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
644 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700645
646 return args
647
648
649def Cleanup():
650 for i in OPTIONS.tempfiles:
651 if os.path.isdir(i):
652 shutil.rmtree(i)
653 else:
654 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700655
656
657class PasswordManager(object):
658 def __init__(self):
659 self.editor = os.getenv("EDITOR", None)
660 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
661
662 def GetPasswords(self, items):
663 """Get passwords corresponding to each string in 'items',
664 returning a dict. (The dict may have keys in addition to the
665 values in 'items'.)
666
667 Uses the passwords in $ANDROID_PW_FILE if available, letting the
668 user edit that file to add more needed passwords. If no editor is
669 available, or $ANDROID_PW_FILE isn't define, prompts the user
670 interactively in the ordinary way.
671 """
672
673 current = self.ReadFile()
674
675 first = True
676 while True:
677 missing = []
678 for i in items:
679 if i not in current or not current[i]:
680 missing.append(i)
681 # Are all the passwords already in the file?
682 if not missing: return current
683
684 for i in missing:
685 current[i] = ""
686
687 if not first:
688 print "key file %s still missing some passwords." % (self.pwfile,)
689 answer = raw_input("try to edit again? [y]> ").strip()
690 if answer and answer[0] not in 'yY':
691 raise RuntimeError("key passwords unavailable")
692 first = False
693
694 current = self.UpdateAndReadFile(current)
695
696 def PromptResult(self, current):
697 """Prompt the user to enter a value (password) for each key in
698 'current' whose value is fales. Returns a new dict with all the
699 values.
700 """
701 result = {}
702 for k, v in sorted(current.iteritems()):
703 if v:
704 result[k] = v
705 else:
706 while True:
707 result[k] = getpass.getpass("Enter password for %s key> "
708 % (k,)).strip()
709 if result[k]: break
710 return result
711
712 def UpdateAndReadFile(self, current):
713 if not self.editor or not self.pwfile:
714 return self.PromptResult(current)
715
716 f = open(self.pwfile, "w")
717 os.chmod(self.pwfile, 0600)
718 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
719 f.write("# (Additional spaces are harmless.)\n\n")
720
721 first_line = None
722 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
723 sorted.sort()
724 for i, (_, k, v) in enumerate(sorted):
725 f.write("[[[ %s ]]] %s\n" % (v, k))
726 if not v and first_line is None:
727 # position cursor on first line with no password.
728 first_line = i + 4
729 f.close()
730
731 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
732 _, _ = p.communicate()
733
734 return self.ReadFile()
735
736 def ReadFile(self):
737 result = {}
738 if self.pwfile is None: return result
739 try:
740 f = open(self.pwfile, "r")
741 for line in f:
742 line = line.strip()
743 if not line or line[0] == '#': continue
744 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
745 if not m:
746 print "failed to parse password file: ", line
747 else:
748 result[m.group(2)] = m.group(1)
749 f.close()
750 except IOError, e:
751 if e.errno != errno.ENOENT:
752 print "error reading password file: ", str(e)
753 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700754
755
Geremy Condra36bd3652014-02-06 19:45:10 -0800756def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700757 # use a fixed timestamp so the output is repeatable.
758 zinfo = zipfile.ZipInfo(filename=filename,
759 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800760 if compression is None:
761 zinfo.compress_type = zip.compression
762 else:
763 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700764 zinfo.external_attr = perms << 16
765 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700766
767
768class DeviceSpecificParams(object):
769 module = None
770 def __init__(self, **kwargs):
771 """Keyword arguments to the constructor become attributes of this
772 object, which is passed to all functions in the device-specific
773 module."""
774 for k, v in kwargs.iteritems():
775 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800776 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700777
778 if self.module is None:
779 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700780 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700781 try:
782 if os.path.isdir(path):
783 info = imp.find_module("releasetools", [path])
784 else:
785 d, f = os.path.split(path)
786 b, x = os.path.splitext(f)
787 if x == ".py":
788 f = b
789 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800790 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700791 self.module = imp.load_module("device_specific", *info)
792 except ImportError:
793 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700794
795 def _DoCall(self, function_name, *args, **kwargs):
796 """Call the named function in the device-specific module, passing
797 the given args and kwargs. The first argument to the call will be
798 the DeviceSpecific object itself. If there is no module, or the
799 module does not define the function, return the value of the
800 'default' kwarg (which itself defaults to None)."""
801 if self.module is None or not hasattr(self.module, function_name):
802 return kwargs.get("default", None)
803 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
804
805 def FullOTA_Assertions(self):
806 """Called after emitting the block of assertions at the top of a
807 full OTA package. Implementations can add whatever additional
808 assertions they like."""
809 return self._DoCall("FullOTA_Assertions")
810
Doug Zongkere5ff5902012-01-17 10:55:37 -0800811 def FullOTA_InstallBegin(self):
812 """Called at the start of full OTA installation."""
813 return self._DoCall("FullOTA_InstallBegin")
814
Doug Zongker05d3dea2009-06-22 11:32:31 -0700815 def FullOTA_InstallEnd(self):
816 """Called at the end of full OTA installation; typically this is
817 used to install the image for the device's baseband processor."""
818 return self._DoCall("FullOTA_InstallEnd")
819
820 def IncrementalOTA_Assertions(self):
821 """Called after emitting the block of assertions at the top of an
822 incremental OTA package. Implementations can add whatever
823 additional assertions they like."""
824 return self._DoCall("IncrementalOTA_Assertions")
825
Doug Zongkere5ff5902012-01-17 10:55:37 -0800826 def IncrementalOTA_VerifyBegin(self):
827 """Called at the start of the verification phase of incremental
828 OTA installation; additional checks can be placed here to abort
829 the script before any changes are made."""
830 return self._DoCall("IncrementalOTA_VerifyBegin")
831
Doug Zongker05d3dea2009-06-22 11:32:31 -0700832 def IncrementalOTA_VerifyEnd(self):
833 """Called at the end of the verification phase of incremental OTA
834 installation; additional checks can be placed here to abort the
835 script before any changes are made."""
836 return self._DoCall("IncrementalOTA_VerifyEnd")
837
Doug Zongkere5ff5902012-01-17 10:55:37 -0800838 def IncrementalOTA_InstallBegin(self):
839 """Called at the start of incremental OTA installation (after
840 verification is complete)."""
841 return self._DoCall("IncrementalOTA_InstallBegin")
842
Doug Zongker05d3dea2009-06-22 11:32:31 -0700843 def IncrementalOTA_InstallEnd(self):
844 """Called at the end of incremental OTA installation; typically
845 this is used to install the image for the device's baseband
846 processor."""
847 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700848
849class File(object):
850 def __init__(self, name, data):
851 self.name = name
852 self.data = data
853 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800854 self.sha1 = sha1(data).hexdigest()
855
856 @classmethod
857 def FromLocalFile(cls, name, diskname):
858 f = open(diskname, "rb")
859 data = f.read()
860 f.close()
861 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700862
863 def WriteToTemp(self):
864 t = tempfile.NamedTemporaryFile()
865 t.write(self.data)
866 t.flush()
867 return t
868
Geremy Condra36bd3652014-02-06 19:45:10 -0800869 def AddToZip(self, z, compression=None):
870 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700871
872DIFF_PROGRAM_BY_EXT = {
873 ".gz" : "imgdiff",
874 ".zip" : ["imgdiff", "-z"],
875 ".jar" : ["imgdiff", "-z"],
876 ".apk" : ["imgdiff", "-z"],
877 ".img" : "imgdiff",
878 }
879
880class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700881 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700882 self.tf = tf
883 self.sf = sf
884 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700885 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700886
887 def ComputePatch(self):
888 """Compute the patch (as a string of data) needed to turn sf into
889 tf. Returns the same tuple as GetPatch()."""
890
891 tf = self.tf
892 sf = self.sf
893
Doug Zongker24cd2802012-08-14 16:36:15 -0700894 if self.diff_program:
895 diff_program = self.diff_program
896 else:
897 ext = os.path.splitext(tf.name)[1]
898 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700899
900 ttemp = tf.WriteToTemp()
901 stemp = sf.WriteToTemp()
902
903 ext = os.path.splitext(tf.name)[1]
904
905 try:
906 ptemp = tempfile.NamedTemporaryFile()
907 if isinstance(diff_program, list):
908 cmd = copy.copy(diff_program)
909 else:
910 cmd = [diff_program]
911 cmd.append(stemp.name)
912 cmd.append(ttemp.name)
913 cmd.append(ptemp.name)
914 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700915 err = []
916 def run():
917 _, e = p.communicate()
918 if e: err.append(e)
919 th = threading.Thread(target=run)
920 th.start()
921 th.join(timeout=300) # 5 mins
922 if th.is_alive():
923 print "WARNING: diff command timed out"
924 p.terminate()
925 th.join(5)
926 if th.is_alive():
927 p.kill()
928 th.join()
929
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700930 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700931 print "WARNING: failure running %s:\n%s\n" % (
932 diff_program, "".join(err))
933 self.patch = None
934 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700935 diff = ptemp.read()
936 finally:
937 ptemp.close()
938 stemp.close()
939 ttemp.close()
940
941 self.patch = diff
942 return self.tf, self.sf, self.patch
943
944
945 def GetPatch(self):
946 """Return a tuple (target_file, source_file, patch_data).
947 patch_data may be None if ComputePatch hasn't been called, or if
948 computing the patch failed."""
949 return self.tf, self.sf, self.patch
950
951
952def ComputeDifferences(diffs):
953 """Call ComputePatch on all the Difference objects in 'diffs'."""
954 print len(diffs), "diffs to compute"
955
956 # Do the largest files first, to try and reduce the long-pole effect.
957 by_size = [(i.tf.size, i) for i in diffs]
958 by_size.sort(reverse=True)
959 by_size = [i[1] for i in by_size]
960
961 lock = threading.Lock()
962 diff_iter = iter(by_size) # accessed under lock
963
964 def worker():
965 try:
966 lock.acquire()
967 for d in diff_iter:
968 lock.release()
969 start = time.time()
970 d.ComputePatch()
971 dur = time.time() - start
972 lock.acquire()
973
974 tf, sf, patch = d.GetPatch()
975 if sf.name == tf.name:
976 name = tf.name
977 else:
978 name = "%s (%s)" % (tf.name, sf.name)
979 if patch is None:
980 print "patching failed! %s" % (name,)
981 else:
982 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
983 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
984 lock.release()
985 except Exception, e:
986 print e
987 raise
988
989 # start worker threads; wait for them all to finish.
990 threads = [threading.Thread(target=worker)
991 for i in range(OPTIONS.worker_threads)]
992 for th in threads:
993 th.start()
994 while threads:
995 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700996
997
998# map recovery.fstab's fs_types to mount/format "partition types"
999PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001000 "ext4": "EMMC", "emmc": "EMMC",
1001 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001002
1003def GetTypeAndDevice(mount_point, info):
1004 fstab = info["fstab"]
1005 if fstab:
1006 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1007 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001008 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001009
1010
1011def ParseCertificate(data):
1012 """Parse a PEM-format certificate."""
1013 cert = []
1014 save = False
1015 for line in data.split("\n"):
1016 if "--END CERTIFICATE--" in line:
1017 break
1018 if save:
1019 cert.append(line)
1020 if "--BEGIN CERTIFICATE--" in line:
1021 save = True
1022 cert = "".join(cert).decode('base64')
1023 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001024
Geremy Condra36bd3652014-02-06 19:45:10 -08001025def XDelta3(source_path, target_path, output_path):
1026 diff_program = ["xdelta3", "-0", "-B", str(64<<20), "-e", "-f", "-s"]
1027 diff_program.append(source_path)
1028 diff_program.append(target_path)
1029 diff_program.append(output_path)
1030 p = Run(diff_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1031 p.communicate()
1032 assert p.returncode == 0, "Couldn't produce patch"
1033
1034def XZ(path):
1035 compress_program = ["xz", "-zk", "-9", "--check=crc32"]
1036 compress_program.append(path)
1037 p = Run(compress_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1038 p.communicate()
1039 assert p.returncode == 0, "Couldn't compress patch"
1040
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001041def MakePartitionPatch(source_file, target_file, partition):
Geremy Condra36bd3652014-02-06 19:45:10 -08001042 with tempfile.NamedTemporaryFile() as output_file:
1043 XDelta3(source_file.name, target_file.name, output_file.name)
1044 XZ(output_file.name)
1045 with open(output_file.name + ".xz") as patch_file:
1046 patch_data = patch_file.read()
1047 os.unlink(patch_file.name)
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001048 return File(partition + ".muimg.p", patch_data)
Doug Zongkerc9253822014-02-04 12:17:58 -08001049
Doug Zongker412c02f2014-02-13 10:58:24 -08001050def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1051 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001052 """Generate a binary patch that creates the recovery image starting
1053 with the boot image. (Most of the space in these images is just the
1054 kernel, which is identical for the two, so the resulting patch
1055 should be efficient.) Add it to the output zip, along with a shell
1056 script that is run from init.rc on first boot to actually do the
1057 patching and install the new recovery image.
1058
1059 recovery_img and boot_img should be File objects for the
1060 corresponding images. info should be the dictionary returned by
1061 common.LoadInfoDict() on the input target_files.
1062 """
1063
Doug Zongker412c02f2014-02-13 10:58:24 -08001064 if info_dict is None:
1065 info_dict = OPTIONS.info_dict
1066
Doug Zongkerc9253822014-02-04 12:17:58 -08001067 diff_program = ["imgdiff"]
1068 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1069 if os.path.exists(path):
1070 diff_program.append("-b")
1071 diff_program.append(path)
1072 bonus_args = "-b /system/etc/recovery-resource.dat"
1073 else:
1074 bonus_args = ""
1075
1076 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1077 _, _, patch = d.ComputePatch()
1078 output_sink("recovery-from-boot.p", patch)
1079
Ying Wanga961a092014-07-29 11:42:37 -07001080 td_pair = GetTypeAndDevice("/boot", info_dict)
1081 if not td_pair:
1082 return
1083 boot_type, boot_device = td_pair
1084 td_pair = GetTypeAndDevice("/recovery", info_dict)
1085 if not td_pair:
1086 return
1087 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001088
1089 sh = """#!/system/bin/sh
1090if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1091 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"
1092else
1093 log -t recovery "Recovery image already installed"
1094fi
1095""" % { 'boot_size': boot_img.size,
1096 'boot_sha1': boot_img.sha1,
1097 'recovery_size': recovery_img.size,
1098 'recovery_sha1': recovery_img.sha1,
1099 'boot_type': boot_type,
1100 'boot_device': boot_device,
1101 'recovery_type': recovery_type,
1102 'recovery_device': recovery_device,
1103 'bonus_args': bonus_args,
1104 }
1105
1106 # The install script location moved from /system/etc to /system/bin
1107 # in the L release. Parse the init.rc file to find out where the
1108 # target-files expects it to be, and put it there.
1109 sh_location = "etc/install-recovery.sh"
1110 try:
1111 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1112 for line in f:
1113 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1114 if m:
1115 sh_location = m.group(1)
1116 print "putting script in", sh_location
1117 break
1118 except (OSError, IOError), e:
1119 print "failed to read init.rc: %s" % (e,)
1120
1121 output_sink(sh_location, sh)