blob: 947a9e42bf9163b1f4e42568198090643bee6334 [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
338 img.seek(os.SEEK_SET, 0)
339 data = img.read()
340
341 ramdisk_img.close()
342 img.close()
343
344 return data
345
346
Doug Zongkerd5131602012-08-02 14:46:42 -0700347def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
348 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800349 """Return a File object (with name 'name') with the desired bootable
350 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
351 'prebuilt_name', otherwise construct it from the source files in
352 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700353
Doug Zongker55d93282011-01-25 17:03:34 -0800354 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
355 if os.path.exists(prebuilt_path):
356 print "using prebuilt %s..." % (prebuilt_name,)
357 return File.FromLocalFile(name, prebuilt_path)
358 else:
359 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700360 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Ying Wangd89ffa82014-02-05 11:28:51 -0800361 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
362 os.path.join(unpack_dir, fs_config),
Ying Wang64a55ba2014-02-05 12:15:06 -0800363 info_dict)
Ying Wangd89ffa82014-02-05 11:28:51 -0800364 if data:
365 return File(name, data)
366 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800367
Doug Zongkereef39442009-04-02 12:14:19 -0700368
Doug Zongker75f17362009-12-08 13:46:44 -0800369def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800370 """Unzip the given archive into a temporary directory and return the name.
371
372 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
373 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
374
375 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
376 main file), open for reading.
377 """
Doug Zongkereef39442009-04-02 12:14:19 -0700378
379 tmp = tempfile.mkdtemp(prefix="targetfiles-")
380 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800381
382 def unzip_to_dir(filename, dirname):
383 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
384 if pattern is not None:
385 cmd.append(pattern)
386 p = Run(cmd, stdout=subprocess.PIPE)
387 p.communicate()
388 if p.returncode != 0:
389 raise ExternalError("failed to unzip input target-files \"%s\"" %
390 (filename,))
391
392 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
393 if m:
394 unzip_to_dir(m.group(1), tmp)
395 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
396 filename = m.group(1)
397 else:
398 unzip_to_dir(filename, tmp)
399
400 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700401
402
403def GetKeyPasswords(keylist):
404 """Given a list of keys, prompt the user to enter passwords for
405 those which require them. Return a {key: password} dict. password
406 will be None if the key has no password."""
407
Doug Zongker8ce7c252009-05-22 13:34:54 -0700408 no_passwords = []
409 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700410 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700411 devnull = open("/dev/null", "w+b")
412 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800413 # We don't need a password for things that aren't really keys.
414 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700415 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700416 continue
417
T.R. Fullhart37e10522013-03-18 10:31:26 -0700418 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700419 "-inform", "DER", "-nocrypt"],
420 stdin=devnull.fileno(),
421 stdout=devnull.fileno(),
422 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700423 p.communicate()
424 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700425 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700426 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700427 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700428 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
429 "-inform", "DER", "-passin", "pass:"],
430 stdin=devnull.fileno(),
431 stdout=devnull.fileno(),
432 stderr=subprocess.PIPE)
433 stdout, stderr = p.communicate()
434 if p.returncode == 0:
435 # Encrypted key with empty string as password.
436 key_passwords[k] = ''
437 elif stderr.startswith('Error decrypting key'):
438 # Definitely encrypted key.
439 # It would have said "Error reading key" if it didn't parse correctly.
440 need_passwords.append(k)
441 else:
442 # Potentially, a type of key that openssl doesn't understand.
443 # We'll let the routines in signapk.jar handle it.
444 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700445 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700446
T.R. Fullhart37e10522013-03-18 10:31:26 -0700447 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700448 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700449 return key_passwords
450
451
Doug Zongker951495f2009-08-14 12:44:19 -0700452def SignFile(input_name, output_name, key, password, align=None,
453 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700454 """Sign the input_name zip/jar/apk, producing output_name. Use the
455 given key and password (the latter may be None if the key does not
456 have a password.
457
458 If align is an integer > 1, zipalign is run to align stored files in
459 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700460
461 If whole_file is true, use the "-w" option to SignApk to embed a
462 signature that covers the whole file in the archive comment of the
463 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700464 """
Doug Zongker951495f2009-08-14 12:44:19 -0700465
Doug Zongkereef39442009-04-02 12:14:19 -0700466 if align == 0 or align == 1:
467 align = None
468
469 if align:
470 temp = tempfile.NamedTemporaryFile()
471 sign_name = temp.name
472 else:
473 sign_name = output_name
474
T.R. Fullhart37e10522013-03-18 10:31:26 -0700475 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
476 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
477 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700478 if whole_file:
479 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700480 cmd.extend([key + OPTIONS.public_key_suffix,
481 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700482 input_name, sign_name])
483
484 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700485 if password is not None:
486 password += "\n"
487 p.communicate(password)
488 if p.returncode != 0:
489 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
490
491 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700492 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700493 p.communicate()
494 if p.returncode != 0:
495 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
496 temp.close()
497
498
Doug Zongker37974732010-09-16 17:44:38 -0700499def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700500 """Check the data string passed against the max size limit, if
501 any, for the given target. Raise exception if the data is too big.
502 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700503
Doug Zongker1684d9c2010-09-17 07:44:38 -0700504 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700505 mount_point = "/" + target
506
Ying Wangf8824af2014-06-03 14:07:27 -0700507 fs_type = None
508 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700509 if info_dict["fstab"]:
510 if mount_point == "/userdata": mount_point = "/data"
511 p = info_dict["fstab"][mount_point]
512 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800513 device = p.device
514 if "/" in device:
515 device = device[device.rfind("/")+1:]
516 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700517 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700518
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700519 if fs_type == "yaffs2":
520 # image size should be increased by 1/64th to account for the
521 # spare area (64 bytes per 2k page)
522 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800523 size = len(data)
524 pct = float(size) * 100.0 / limit
525 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
526 if pct >= 99.0:
527 raise ExternalError(msg)
528 elif pct >= 95.0:
529 print
530 print " WARNING: ", msg
531 print
532 elif OPTIONS.verbose:
533 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700534
535
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800536def ReadApkCerts(tf_zip):
537 """Given a target_files ZipFile, parse the META/apkcerts.txt file
538 and return a {package: cert} dict."""
539 certmap = {}
540 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
541 line = line.strip()
542 if not line: continue
543 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
544 r'private_key="(.*)"$', line)
545 if m:
546 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700547 public_key_suffix_len = len(OPTIONS.public_key_suffix)
548 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800549 if cert in SPECIAL_CERT_STRINGS and not privkey:
550 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700551 elif (cert.endswith(OPTIONS.public_key_suffix) and
552 privkey.endswith(OPTIONS.private_key_suffix) and
553 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
554 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800555 else:
556 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
557 return certmap
558
559
Doug Zongkereef39442009-04-02 12:14:19 -0700560COMMON_DOCSTRING = """
561 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700562 Prepend <dir>/bin to the list of places to search for binaries
563 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700564
Doug Zongker05d3dea2009-06-22 11:32:31 -0700565 -s (--device_specific) <file>
566 Path to the python module containing device-specific
567 releasetools code.
568
Doug Zongker8bec09e2009-11-30 15:37:14 -0800569 -x (--extra) <key=value>
570 Add a key/value pair to the 'extras' dict, which device-specific
571 extension code may look at.
572
Doug Zongkereef39442009-04-02 12:14:19 -0700573 -v (--verbose)
574 Show command lines being executed.
575
576 -h (--help)
577 Display this usage message and exit.
578"""
579
580def Usage(docstring):
581 print docstring.rstrip("\n")
582 print COMMON_DOCSTRING
583
584
585def ParseOptions(argv,
586 docstring,
587 extra_opts="", extra_long_opts=(),
588 extra_option_handler=None):
589 """Parse the options in argv and return any arguments that aren't
590 flags. docstring is the calling module's docstring, to be displayed
591 for errors and -h. extra_opts and extra_long_opts are for flags
592 defined by the caller, which are processed by passing them to
593 extra_option_handler."""
594
595 try:
596 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800597 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700598 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
599 "java_path=", "public_key_suffix=", "private_key_suffix=",
600 "device_specific=", "extra="] +
601 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700602 except getopt.GetoptError, err:
603 Usage(docstring)
604 print "**", str(err), "**"
605 sys.exit(2)
606
607 path_specified = False
608
609 for o, a in opts:
610 if o in ("-h", "--help"):
611 Usage(docstring)
612 sys.exit()
613 elif o in ("-v", "--verbose"):
614 OPTIONS.verbose = True
615 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700616 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700617 elif o in ("--signapk_path",):
618 OPTIONS.signapk_path = a
619 elif o in ("--extra_signapk_args",):
620 OPTIONS.extra_signapk_args = shlex.split(a)
621 elif o in ("--java_path",):
622 OPTIONS.java_path = a
623 elif o in ("--public_key_suffix",):
624 OPTIONS.public_key_suffix = a
625 elif o in ("--private_key_suffix",):
626 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700627 elif o in ("-s", "--device_specific"):
628 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800629 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800630 key, value = a.split("=", 1)
631 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700632 else:
633 if extra_option_handler is None or not extra_option_handler(o, a):
634 assert False, "unknown option \"%s\"" % (o,)
635
Doug Zongker602a84e2009-06-18 08:35:12 -0700636 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
637 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700638
639 return args
640
641
642def Cleanup():
643 for i in OPTIONS.tempfiles:
644 if os.path.isdir(i):
645 shutil.rmtree(i)
646 else:
647 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700648
649
650class PasswordManager(object):
651 def __init__(self):
652 self.editor = os.getenv("EDITOR", None)
653 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
654
655 def GetPasswords(self, items):
656 """Get passwords corresponding to each string in 'items',
657 returning a dict. (The dict may have keys in addition to the
658 values in 'items'.)
659
660 Uses the passwords in $ANDROID_PW_FILE if available, letting the
661 user edit that file to add more needed passwords. If no editor is
662 available, or $ANDROID_PW_FILE isn't define, prompts the user
663 interactively in the ordinary way.
664 """
665
666 current = self.ReadFile()
667
668 first = True
669 while True:
670 missing = []
671 for i in items:
672 if i not in current or not current[i]:
673 missing.append(i)
674 # Are all the passwords already in the file?
675 if not missing: return current
676
677 for i in missing:
678 current[i] = ""
679
680 if not first:
681 print "key file %s still missing some passwords." % (self.pwfile,)
682 answer = raw_input("try to edit again? [y]> ").strip()
683 if answer and answer[0] not in 'yY':
684 raise RuntimeError("key passwords unavailable")
685 first = False
686
687 current = self.UpdateAndReadFile(current)
688
689 def PromptResult(self, current):
690 """Prompt the user to enter a value (password) for each key in
691 'current' whose value is fales. Returns a new dict with all the
692 values.
693 """
694 result = {}
695 for k, v in sorted(current.iteritems()):
696 if v:
697 result[k] = v
698 else:
699 while True:
700 result[k] = getpass.getpass("Enter password for %s key> "
701 % (k,)).strip()
702 if result[k]: break
703 return result
704
705 def UpdateAndReadFile(self, current):
706 if not self.editor or not self.pwfile:
707 return self.PromptResult(current)
708
709 f = open(self.pwfile, "w")
710 os.chmod(self.pwfile, 0600)
711 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
712 f.write("# (Additional spaces are harmless.)\n\n")
713
714 first_line = None
715 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
716 sorted.sort()
717 for i, (_, k, v) in enumerate(sorted):
718 f.write("[[[ %s ]]] %s\n" % (v, k))
719 if not v and first_line is None:
720 # position cursor on first line with no password.
721 first_line = i + 4
722 f.close()
723
724 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
725 _, _ = p.communicate()
726
727 return self.ReadFile()
728
729 def ReadFile(self):
730 result = {}
731 if self.pwfile is None: return result
732 try:
733 f = open(self.pwfile, "r")
734 for line in f:
735 line = line.strip()
736 if not line or line[0] == '#': continue
737 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
738 if not m:
739 print "failed to parse password file: ", line
740 else:
741 result[m.group(2)] = m.group(1)
742 f.close()
743 except IOError, e:
744 if e.errno != errno.ENOENT:
745 print "error reading password file: ", str(e)
746 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700747
748
Geremy Condra36bd3652014-02-06 19:45:10 -0800749def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700750 # use a fixed timestamp so the output is repeatable.
751 zinfo = zipfile.ZipInfo(filename=filename,
752 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800753 if compression is None:
754 zinfo.compress_type = zip.compression
755 else:
756 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700757 zinfo.external_attr = perms << 16
758 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700759
760
761class DeviceSpecificParams(object):
762 module = None
763 def __init__(self, **kwargs):
764 """Keyword arguments to the constructor become attributes of this
765 object, which is passed to all functions in the device-specific
766 module."""
767 for k, v in kwargs.iteritems():
768 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800769 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700770
771 if self.module is None:
772 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700773 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700774 try:
775 if os.path.isdir(path):
776 info = imp.find_module("releasetools", [path])
777 else:
778 d, f = os.path.split(path)
779 b, x = os.path.splitext(f)
780 if x == ".py":
781 f = b
782 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800783 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700784 self.module = imp.load_module("device_specific", *info)
785 except ImportError:
786 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700787
788 def _DoCall(self, function_name, *args, **kwargs):
789 """Call the named function in the device-specific module, passing
790 the given args and kwargs. The first argument to the call will be
791 the DeviceSpecific object itself. If there is no module, or the
792 module does not define the function, return the value of the
793 'default' kwarg (which itself defaults to None)."""
794 if self.module is None or not hasattr(self.module, function_name):
795 return kwargs.get("default", None)
796 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
797
798 def FullOTA_Assertions(self):
799 """Called after emitting the block of assertions at the top of a
800 full OTA package. Implementations can add whatever additional
801 assertions they like."""
802 return self._DoCall("FullOTA_Assertions")
803
Doug Zongkere5ff5902012-01-17 10:55:37 -0800804 def FullOTA_InstallBegin(self):
805 """Called at the start of full OTA installation."""
806 return self._DoCall("FullOTA_InstallBegin")
807
Doug Zongker05d3dea2009-06-22 11:32:31 -0700808 def FullOTA_InstallEnd(self):
809 """Called at the end of full OTA installation; typically this is
810 used to install the image for the device's baseband processor."""
811 return self._DoCall("FullOTA_InstallEnd")
812
813 def IncrementalOTA_Assertions(self):
814 """Called after emitting the block of assertions at the top of an
815 incremental OTA package. Implementations can add whatever
816 additional assertions they like."""
817 return self._DoCall("IncrementalOTA_Assertions")
818
Doug Zongkere5ff5902012-01-17 10:55:37 -0800819 def IncrementalOTA_VerifyBegin(self):
820 """Called at the start of the verification phase of incremental
821 OTA installation; additional checks can be placed here to abort
822 the script before any changes are made."""
823 return self._DoCall("IncrementalOTA_VerifyBegin")
824
Doug Zongker05d3dea2009-06-22 11:32:31 -0700825 def IncrementalOTA_VerifyEnd(self):
826 """Called at the end of the verification phase of incremental OTA
827 installation; additional checks can be placed here to abort the
828 script before any changes are made."""
829 return self._DoCall("IncrementalOTA_VerifyEnd")
830
Doug Zongkere5ff5902012-01-17 10:55:37 -0800831 def IncrementalOTA_InstallBegin(self):
832 """Called at the start of incremental OTA installation (after
833 verification is complete)."""
834 return self._DoCall("IncrementalOTA_InstallBegin")
835
Doug Zongker05d3dea2009-06-22 11:32:31 -0700836 def IncrementalOTA_InstallEnd(self):
837 """Called at the end of incremental OTA installation; typically
838 this is used to install the image for the device's baseband
839 processor."""
840 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700841
842class File(object):
843 def __init__(self, name, data):
844 self.name = name
845 self.data = data
846 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800847 self.sha1 = sha1(data).hexdigest()
848
849 @classmethod
850 def FromLocalFile(cls, name, diskname):
851 f = open(diskname, "rb")
852 data = f.read()
853 f.close()
854 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700855
856 def WriteToTemp(self):
857 t = tempfile.NamedTemporaryFile()
858 t.write(self.data)
859 t.flush()
860 return t
861
Geremy Condra36bd3652014-02-06 19:45:10 -0800862 def AddToZip(self, z, compression=None):
863 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700864
865DIFF_PROGRAM_BY_EXT = {
866 ".gz" : "imgdiff",
867 ".zip" : ["imgdiff", "-z"],
868 ".jar" : ["imgdiff", "-z"],
869 ".apk" : ["imgdiff", "-z"],
870 ".img" : "imgdiff",
871 }
872
873class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700874 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700875 self.tf = tf
876 self.sf = sf
877 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700878 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700879
880 def ComputePatch(self):
881 """Compute the patch (as a string of data) needed to turn sf into
882 tf. Returns the same tuple as GetPatch()."""
883
884 tf = self.tf
885 sf = self.sf
886
Doug Zongker24cd2802012-08-14 16:36:15 -0700887 if self.diff_program:
888 diff_program = self.diff_program
889 else:
890 ext = os.path.splitext(tf.name)[1]
891 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700892
893 ttemp = tf.WriteToTemp()
894 stemp = sf.WriteToTemp()
895
896 ext = os.path.splitext(tf.name)[1]
897
898 try:
899 ptemp = tempfile.NamedTemporaryFile()
900 if isinstance(diff_program, list):
901 cmd = copy.copy(diff_program)
902 else:
903 cmd = [diff_program]
904 cmd.append(stemp.name)
905 cmd.append(ttemp.name)
906 cmd.append(ptemp.name)
907 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700908 err = []
909 def run():
910 _, e = p.communicate()
911 if e: err.append(e)
912 th = threading.Thread(target=run)
913 th.start()
914 th.join(timeout=300) # 5 mins
915 if th.is_alive():
916 print "WARNING: diff command timed out"
917 p.terminate()
918 th.join(5)
919 if th.is_alive():
920 p.kill()
921 th.join()
922
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700923 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700924 print "WARNING: failure running %s:\n%s\n" % (
925 diff_program, "".join(err))
926 self.patch = None
927 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700928 diff = ptemp.read()
929 finally:
930 ptemp.close()
931 stemp.close()
932 ttemp.close()
933
934 self.patch = diff
935 return self.tf, self.sf, self.patch
936
937
938 def GetPatch(self):
939 """Return a tuple (target_file, source_file, patch_data).
940 patch_data may be None if ComputePatch hasn't been called, or if
941 computing the patch failed."""
942 return self.tf, self.sf, self.patch
943
944
945def ComputeDifferences(diffs):
946 """Call ComputePatch on all the Difference objects in 'diffs'."""
947 print len(diffs), "diffs to compute"
948
949 # Do the largest files first, to try and reduce the long-pole effect.
950 by_size = [(i.tf.size, i) for i in diffs]
951 by_size.sort(reverse=True)
952 by_size = [i[1] for i in by_size]
953
954 lock = threading.Lock()
955 diff_iter = iter(by_size) # accessed under lock
956
957 def worker():
958 try:
959 lock.acquire()
960 for d in diff_iter:
961 lock.release()
962 start = time.time()
963 d.ComputePatch()
964 dur = time.time() - start
965 lock.acquire()
966
967 tf, sf, patch = d.GetPatch()
968 if sf.name == tf.name:
969 name = tf.name
970 else:
971 name = "%s (%s)" % (tf.name, sf.name)
972 if patch is None:
973 print "patching failed! %s" % (name,)
974 else:
975 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
976 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
977 lock.release()
978 except Exception, e:
979 print e
980 raise
981
982 # start worker threads; wait for them all to finish.
983 threads = [threading.Thread(target=worker)
984 for i in range(OPTIONS.worker_threads)]
985 for th in threads:
986 th.start()
987 while threads:
988 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700989
990
991# map recovery.fstab's fs_types to mount/format "partition types"
992PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -0700993 "ext4": "EMMC", "emmc": "EMMC",
994 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -0700995
996def GetTypeAndDevice(mount_point, info):
997 fstab = info["fstab"]
998 if fstab:
999 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1000 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001001 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001002
1003
1004def ParseCertificate(data):
1005 """Parse a PEM-format certificate."""
1006 cert = []
1007 save = False
1008 for line in data.split("\n"):
1009 if "--END CERTIFICATE--" in line:
1010 break
1011 if save:
1012 cert.append(line)
1013 if "--BEGIN CERTIFICATE--" in line:
1014 save = True
1015 cert = "".join(cert).decode('base64')
1016 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001017
Geremy Condra36bd3652014-02-06 19:45:10 -08001018def XDelta3(source_path, target_path, output_path):
1019 diff_program = ["xdelta3", "-0", "-B", str(64<<20), "-e", "-f", "-s"]
1020 diff_program.append(source_path)
1021 diff_program.append(target_path)
1022 diff_program.append(output_path)
1023 p = Run(diff_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1024 p.communicate()
1025 assert p.returncode == 0, "Couldn't produce patch"
1026
1027def XZ(path):
1028 compress_program = ["xz", "-zk", "-9", "--check=crc32"]
1029 compress_program.append(path)
1030 p = Run(compress_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1031 p.communicate()
1032 assert p.returncode == 0, "Couldn't compress patch"
1033
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001034def MakePartitionPatch(source_file, target_file, partition):
Geremy Condra36bd3652014-02-06 19:45:10 -08001035 with tempfile.NamedTemporaryFile() as output_file:
1036 XDelta3(source_file.name, target_file.name, output_file.name)
1037 XZ(output_file.name)
1038 with open(output_file.name + ".xz") as patch_file:
1039 patch_data = patch_file.read()
1040 os.unlink(patch_file.name)
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001041 return File(partition + ".muimg.p", patch_data)
Doug Zongkerc9253822014-02-04 12:17:58 -08001042
Doug Zongker412c02f2014-02-13 10:58:24 -08001043def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1044 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001045 """Generate a binary patch that creates the recovery image starting
1046 with the boot image. (Most of the space in these images is just the
1047 kernel, which is identical for the two, so the resulting patch
1048 should be efficient.) Add it to the output zip, along with a shell
1049 script that is run from init.rc on first boot to actually do the
1050 patching and install the new recovery image.
1051
1052 recovery_img and boot_img should be File objects for the
1053 corresponding images. info should be the dictionary returned by
1054 common.LoadInfoDict() on the input target_files.
1055 """
1056
Doug Zongker412c02f2014-02-13 10:58:24 -08001057 if info_dict is None:
1058 info_dict = OPTIONS.info_dict
1059
Doug Zongkerc9253822014-02-04 12:17:58 -08001060 diff_program = ["imgdiff"]
1061 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1062 if os.path.exists(path):
1063 diff_program.append("-b")
1064 diff_program.append(path)
1065 bonus_args = "-b /system/etc/recovery-resource.dat"
1066 else:
1067 bonus_args = ""
1068
1069 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1070 _, _, patch = d.ComputePatch()
1071 output_sink("recovery-from-boot.p", patch)
1072
Ying Wanga961a092014-07-29 11:42:37 -07001073 td_pair = GetTypeAndDevice("/boot", info_dict)
1074 if not td_pair:
1075 return
1076 boot_type, boot_device = td_pair
1077 td_pair = GetTypeAndDevice("/recovery", info_dict)
1078 if not td_pair:
1079 return
1080 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001081
1082 sh = """#!/system/bin/sh
1083if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1084 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"
1085else
1086 log -t recovery "Recovery image already installed"
1087fi
1088""" % { 'boot_size': boot_img.size,
1089 'boot_sha1': boot_img.sha1,
1090 'recovery_size': recovery_img.size,
1091 'recovery_sha1': recovery_img.sha1,
1092 'boot_type': boot_type,
1093 'boot_device': boot_device,
1094 'recovery_type': recovery_type,
1095 'recovery_device': recovery_device,
1096 'bonus_args': bonus_args,
1097 }
1098
1099 # The install script location moved from /system/etc to /system/bin
1100 # in the L release. Parse the init.rc file to find out where the
1101 # target-files expects it to be, and put it there.
1102 sh_location = "etc/install-recovery.sh"
1103 try:
1104 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1105 for line in f:
1106 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1107 if m:
1108 sh_location = m.group(1)
1109 print "putting script in", sh_location
1110 break
1111 except (OSError, IOError), e:
1112 print "failed to read init.rc: %s" % (e,)
1113
1114 output_sink(sh_location, sh)