blob: efba1fe1cb6a4103e09a37bce8018038bcce7e7d [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")
156 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700157 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700158 makeint("recovery_size")
159 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800160 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700161
Doug Zongkerc9253822014-02-04 12:17:58 -0800162 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
163 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700164 return d
165
Doug Zongkerc9253822014-02-04 12:17:58 -0800166def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700167 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800168 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700169 except KeyError:
170 print "Warning: could not find SYSTEM/build.prop in %s" % zip
171 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700172 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700173
Michael Runge6e836112014-04-15 17:40:21 -0700174def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700175 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700176 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700177 line = line.strip()
178 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700179 if "=" in line:
180 name, value = line.split("=", 1)
181 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700182 return d
183
Doug Zongkerc9253822014-02-04 12:17:58 -0800184def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700185 class Partition(object):
186 pass
187
188 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800189 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700190 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800191 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700192 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700193
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800194 if fstab_version == 1:
195 d = {}
196 for line in data.split("\n"):
197 line = line.strip()
198 if not line or line.startswith("#"): continue
199 pieces = line.split()
200 if not (3 <= len(pieces) <= 4):
201 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700202
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800203 p = Partition()
204 p.mount_point = pieces[0]
205 p.fs_type = pieces[1]
206 p.device = pieces[2]
207 p.length = 0
208 options = None
209 if len(pieces) >= 4:
210 if pieces[3].startswith("/"):
211 p.device2 = pieces[3]
212 if len(pieces) >= 5:
213 options = pieces[4]
214 else:
215 p.device2 = None
216 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800217 else:
218 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700219
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800220 if options:
221 options = options.split(",")
222 for i in options:
223 if i.startswith("length="):
224 p.length = int(i[7:])
225 else:
226 print "%s: unknown option \"%s\"" % (p.mount_point, i)
227
228 d[p.mount_point] = p
229
230 elif fstab_version == 2:
231 d = {}
232 for line in data.split("\n"):
233 line = line.strip()
234 if not line or line.startswith("#"): continue
235 pieces = line.split()
236 if len(pieces) != 5:
237 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
238
239 # Ignore entries that are managed by vold
240 options = pieces[4]
241 if "voldmanaged=" in options: continue
242
243 # It's a good line, parse it
244 p = Partition()
245 p.device = pieces[0]
246 p.mount_point = pieces[1]
247 p.fs_type = pieces[2]
248 p.device2 = None
249 p.length = 0
250
Doug Zongker086cbb02011-02-17 15:54:20 -0800251 options = options.split(",")
252 for i in options:
253 if i.startswith("length="):
254 p.length = int(i[7:])
255 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 # Ignore all unknown options in the unified fstab
257 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800258
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259 d[p.mount_point] = p
260
261 else:
262 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
263
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700264 return d
265
266
Doug Zongker37974732010-09-16 17:44:38 -0700267def DumpInfoDict(d):
268 for k, v in sorted(d.items()):
269 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700270
Doug Zongkerd5131602012-08-02 14:46:42 -0700271def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700272 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700273 'sourcedir'), and turn them into a boot image. Return the image
274 data, or None if sourcedir does not appear to contains files for
275 building the requested image."""
276
277 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
278 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
279 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700280
Doug Zongkerd5131602012-08-02 14:46:42 -0700281 if info_dict is None:
282 info_dict = OPTIONS.info_dict
283
Doug Zongkereef39442009-04-02 12:14:19 -0700284 ramdisk_img = tempfile.NamedTemporaryFile()
285 img = tempfile.NamedTemporaryFile()
286
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700287 if os.access(fs_config_file, os.F_OK):
288 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
289 else:
290 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
291 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700292 p2 = Run(["minigzip"],
293 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700294
295 p2.wait()
296 p1.wait()
297 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700298 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700299
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800300 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
301 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
302
303 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700304
Doug Zongker171f1cd2009-06-15 22:36:37 -0700305 fn = os.path.join(sourcedir, "cmdline")
306 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700307 cmd.append("--cmdline")
308 cmd.append(open(fn).read().rstrip("\n"))
309
310 fn = os.path.join(sourcedir, "base")
311 if os.access(fn, os.F_OK):
312 cmd.append("--base")
313 cmd.append(open(fn).read().rstrip("\n"))
314
Ying Wang4de6b5b2010-08-25 14:29:34 -0700315 fn = os.path.join(sourcedir, "pagesize")
316 if os.access(fn, os.F_OK):
317 cmd.append("--pagesize")
318 cmd.append(open(fn).read().rstrip("\n"))
319
Doug Zongkerd5131602012-08-02 14:46:42 -0700320 args = info_dict.get("mkbootimg_args", None)
321 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700322 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700323
Doug Zongker38a649f2009-06-17 09:07:09 -0700324 cmd.extend(["--ramdisk", ramdisk_img.name,
325 "--output", img.name])
326
327 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700328 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700329 assert p.returncode == 0, "mkbootimg of %s image failed" % (
330 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700331
332 img.seek(os.SEEK_SET, 0)
333 data = img.read()
334
335 ramdisk_img.close()
336 img.close()
337
338 return data
339
340
Doug Zongkerd5131602012-08-02 14:46:42 -0700341def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
342 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800343 """Return a File object (with name 'name') with the desired bootable
344 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
345 'prebuilt_name', otherwise construct it from the source files in
346 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700347
Doug Zongker55d93282011-01-25 17:03:34 -0800348 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
349 if os.path.exists(prebuilt_path):
350 print "using prebuilt %s..." % (prebuilt_name,)
351 return File.FromLocalFile(name, prebuilt_path)
352 else:
353 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700354 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Ying Wangd89ffa82014-02-05 11:28:51 -0800355 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
356 os.path.join(unpack_dir, fs_config),
Ying Wang64a55ba2014-02-05 12:15:06 -0800357 info_dict)
Ying Wangd89ffa82014-02-05 11:28:51 -0800358 if data:
359 return File(name, data)
360 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800361
Doug Zongkereef39442009-04-02 12:14:19 -0700362
Doug Zongker75f17362009-12-08 13:46:44 -0800363def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800364 """Unzip the given archive into a temporary directory and return the name.
365
366 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
367 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
368
369 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
370 main file), open for reading.
371 """
Doug Zongkereef39442009-04-02 12:14:19 -0700372
373 tmp = tempfile.mkdtemp(prefix="targetfiles-")
374 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800375
376 def unzip_to_dir(filename, dirname):
377 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
378 if pattern is not None:
379 cmd.append(pattern)
380 p = Run(cmd, stdout=subprocess.PIPE)
381 p.communicate()
382 if p.returncode != 0:
383 raise ExternalError("failed to unzip input target-files \"%s\"" %
384 (filename,))
385
386 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
387 if m:
388 unzip_to_dir(m.group(1), tmp)
389 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
390 filename = m.group(1)
391 else:
392 unzip_to_dir(filename, tmp)
393
394 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700395
396
397def GetKeyPasswords(keylist):
398 """Given a list of keys, prompt the user to enter passwords for
399 those which require them. Return a {key: password} dict. password
400 will be None if the key has no password."""
401
Doug Zongker8ce7c252009-05-22 13:34:54 -0700402 no_passwords = []
403 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700404 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700405 devnull = open("/dev/null", "w+b")
406 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800407 # We don't need a password for things that aren't really keys.
408 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700409 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700410 continue
411
T.R. Fullhart37e10522013-03-18 10:31:26 -0700412 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700413 "-inform", "DER", "-nocrypt"],
414 stdin=devnull.fileno(),
415 stdout=devnull.fileno(),
416 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700417 p.communicate()
418 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700419 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700420 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700421 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700422 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
423 "-inform", "DER", "-passin", "pass:"],
424 stdin=devnull.fileno(),
425 stdout=devnull.fileno(),
426 stderr=subprocess.PIPE)
427 stdout, stderr = p.communicate()
428 if p.returncode == 0:
429 # Encrypted key with empty string as password.
430 key_passwords[k] = ''
431 elif stderr.startswith('Error decrypting key'):
432 # Definitely encrypted key.
433 # It would have said "Error reading key" if it didn't parse correctly.
434 need_passwords.append(k)
435 else:
436 # Potentially, a type of key that openssl doesn't understand.
437 # We'll let the routines in signapk.jar handle it.
438 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700439 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700440
T.R. Fullhart37e10522013-03-18 10:31:26 -0700441 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700442 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700443 return key_passwords
444
445
Doug Zongker951495f2009-08-14 12:44:19 -0700446def SignFile(input_name, output_name, key, password, align=None,
447 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700448 """Sign the input_name zip/jar/apk, producing output_name. Use the
449 given key and password (the latter may be None if the key does not
450 have a password.
451
452 If align is an integer > 1, zipalign is run to align stored files in
453 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700454
455 If whole_file is true, use the "-w" option to SignApk to embed a
456 signature that covers the whole file in the archive comment of the
457 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700458 """
Doug Zongker951495f2009-08-14 12:44:19 -0700459
Doug Zongkereef39442009-04-02 12:14:19 -0700460 if align == 0 or align == 1:
461 align = None
462
463 if align:
464 temp = tempfile.NamedTemporaryFile()
465 sign_name = temp.name
466 else:
467 sign_name = output_name
468
T.R. Fullhart37e10522013-03-18 10:31:26 -0700469 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
470 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
471 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700472 if whole_file:
473 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700474 cmd.extend([key + OPTIONS.public_key_suffix,
475 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700476 input_name, sign_name])
477
478 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700479 if password is not None:
480 password += "\n"
481 p.communicate(password)
482 if p.returncode != 0:
483 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
484
485 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700486 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700487 p.communicate()
488 if p.returncode != 0:
489 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
490 temp.close()
491
492
Doug Zongker37974732010-09-16 17:44:38 -0700493def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700494 """Check the data string passed against the max size limit, if
495 any, for the given target. Raise exception if the data is too big.
496 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700497
Doug Zongker1684d9c2010-09-17 07:44:38 -0700498 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700499 mount_point = "/" + target
500
Ying Wangf8824af2014-06-03 14:07:27 -0700501 fs_type = None
502 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700503 if info_dict["fstab"]:
504 if mount_point == "/userdata": mount_point = "/data"
505 p = info_dict["fstab"][mount_point]
506 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800507 device = p.device
508 if "/" in device:
509 device = device[device.rfind("/")+1:]
510 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700511 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700512
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700513 if fs_type == "yaffs2":
514 # image size should be increased by 1/64th to account for the
515 # spare area (64 bytes per 2k page)
516 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800517 size = len(data)
518 pct = float(size) * 100.0 / limit
519 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
520 if pct >= 99.0:
521 raise ExternalError(msg)
522 elif pct >= 95.0:
523 print
524 print " WARNING: ", msg
525 print
526 elif OPTIONS.verbose:
527 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700528
529
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800530def ReadApkCerts(tf_zip):
531 """Given a target_files ZipFile, parse the META/apkcerts.txt file
532 and return a {package: cert} dict."""
533 certmap = {}
534 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
535 line = line.strip()
536 if not line: continue
537 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
538 r'private_key="(.*)"$', line)
539 if m:
540 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700541 public_key_suffix_len = len(OPTIONS.public_key_suffix)
542 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800543 if cert in SPECIAL_CERT_STRINGS and not privkey:
544 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700545 elif (cert.endswith(OPTIONS.public_key_suffix) and
546 privkey.endswith(OPTIONS.private_key_suffix) and
547 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
548 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800549 else:
550 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
551 return certmap
552
553
Doug Zongkereef39442009-04-02 12:14:19 -0700554COMMON_DOCSTRING = """
555 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700556 Prepend <dir>/bin to the list of places to search for binaries
557 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700558
Doug Zongker05d3dea2009-06-22 11:32:31 -0700559 -s (--device_specific) <file>
560 Path to the python module containing device-specific
561 releasetools code.
562
Doug Zongker8bec09e2009-11-30 15:37:14 -0800563 -x (--extra) <key=value>
564 Add a key/value pair to the 'extras' dict, which device-specific
565 extension code may look at.
566
Doug Zongkereef39442009-04-02 12:14:19 -0700567 -v (--verbose)
568 Show command lines being executed.
569
570 -h (--help)
571 Display this usage message and exit.
572"""
573
574def Usage(docstring):
575 print docstring.rstrip("\n")
576 print COMMON_DOCSTRING
577
578
579def ParseOptions(argv,
580 docstring,
581 extra_opts="", extra_long_opts=(),
582 extra_option_handler=None):
583 """Parse the options in argv and return any arguments that aren't
584 flags. docstring is the calling module's docstring, to be displayed
585 for errors and -h. extra_opts and extra_long_opts are for flags
586 defined by the caller, which are processed by passing them to
587 extra_option_handler."""
588
589 try:
590 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800591 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700592 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
593 "java_path=", "public_key_suffix=", "private_key_suffix=",
594 "device_specific=", "extra="] +
595 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700596 except getopt.GetoptError, err:
597 Usage(docstring)
598 print "**", str(err), "**"
599 sys.exit(2)
600
601 path_specified = False
602
603 for o, a in opts:
604 if o in ("-h", "--help"):
605 Usage(docstring)
606 sys.exit()
607 elif o in ("-v", "--verbose"):
608 OPTIONS.verbose = True
609 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700610 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700611 elif o in ("--signapk_path",):
612 OPTIONS.signapk_path = a
613 elif o in ("--extra_signapk_args",):
614 OPTIONS.extra_signapk_args = shlex.split(a)
615 elif o in ("--java_path",):
616 OPTIONS.java_path = a
617 elif o in ("--public_key_suffix",):
618 OPTIONS.public_key_suffix = a
619 elif o in ("--private_key_suffix",):
620 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700621 elif o in ("-s", "--device_specific"):
622 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800623 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800624 key, value = a.split("=", 1)
625 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700626 else:
627 if extra_option_handler is None or not extra_option_handler(o, a):
628 assert False, "unknown option \"%s\"" % (o,)
629
Doug Zongker602a84e2009-06-18 08:35:12 -0700630 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
631 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700632
633 return args
634
635
636def Cleanup():
637 for i in OPTIONS.tempfiles:
638 if os.path.isdir(i):
639 shutil.rmtree(i)
640 else:
641 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700642
643
644class PasswordManager(object):
645 def __init__(self):
646 self.editor = os.getenv("EDITOR", None)
647 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
648
649 def GetPasswords(self, items):
650 """Get passwords corresponding to each string in 'items',
651 returning a dict. (The dict may have keys in addition to the
652 values in 'items'.)
653
654 Uses the passwords in $ANDROID_PW_FILE if available, letting the
655 user edit that file to add more needed passwords. If no editor is
656 available, or $ANDROID_PW_FILE isn't define, prompts the user
657 interactively in the ordinary way.
658 """
659
660 current = self.ReadFile()
661
662 first = True
663 while True:
664 missing = []
665 for i in items:
666 if i not in current or not current[i]:
667 missing.append(i)
668 # Are all the passwords already in the file?
669 if not missing: return current
670
671 for i in missing:
672 current[i] = ""
673
674 if not first:
675 print "key file %s still missing some passwords." % (self.pwfile,)
676 answer = raw_input("try to edit again? [y]> ").strip()
677 if answer and answer[0] not in 'yY':
678 raise RuntimeError("key passwords unavailable")
679 first = False
680
681 current = self.UpdateAndReadFile(current)
682
683 def PromptResult(self, current):
684 """Prompt the user to enter a value (password) for each key in
685 'current' whose value is fales. Returns a new dict with all the
686 values.
687 """
688 result = {}
689 for k, v in sorted(current.iteritems()):
690 if v:
691 result[k] = v
692 else:
693 while True:
694 result[k] = getpass.getpass("Enter password for %s key> "
695 % (k,)).strip()
696 if result[k]: break
697 return result
698
699 def UpdateAndReadFile(self, current):
700 if not self.editor or not self.pwfile:
701 return self.PromptResult(current)
702
703 f = open(self.pwfile, "w")
704 os.chmod(self.pwfile, 0600)
705 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
706 f.write("# (Additional spaces are harmless.)\n\n")
707
708 first_line = None
709 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
710 sorted.sort()
711 for i, (_, k, v) in enumerate(sorted):
712 f.write("[[[ %s ]]] %s\n" % (v, k))
713 if not v and first_line is None:
714 # position cursor on first line with no password.
715 first_line = i + 4
716 f.close()
717
718 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
719 _, _ = p.communicate()
720
721 return self.ReadFile()
722
723 def ReadFile(self):
724 result = {}
725 if self.pwfile is None: return result
726 try:
727 f = open(self.pwfile, "r")
728 for line in f:
729 line = line.strip()
730 if not line or line[0] == '#': continue
731 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
732 if not m:
733 print "failed to parse password file: ", line
734 else:
735 result[m.group(2)] = m.group(1)
736 f.close()
737 except IOError, e:
738 if e.errno != errno.ENOENT:
739 print "error reading password file: ", str(e)
740 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700741
742
Geremy Condra36bd3652014-02-06 19:45:10 -0800743def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700744 # use a fixed timestamp so the output is repeatable.
745 zinfo = zipfile.ZipInfo(filename=filename,
746 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800747 if compression is None:
748 zinfo.compress_type = zip.compression
749 else:
750 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700751 zinfo.external_attr = perms << 16
752 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700753
754
755class DeviceSpecificParams(object):
756 module = None
757 def __init__(self, **kwargs):
758 """Keyword arguments to the constructor become attributes of this
759 object, which is passed to all functions in the device-specific
760 module."""
761 for k, v in kwargs.iteritems():
762 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800763 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700764
765 if self.module is None:
766 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700767 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700768 try:
769 if os.path.isdir(path):
770 info = imp.find_module("releasetools", [path])
771 else:
772 d, f = os.path.split(path)
773 b, x = os.path.splitext(f)
774 if x == ".py":
775 f = b
776 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800777 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700778 self.module = imp.load_module("device_specific", *info)
779 except ImportError:
780 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700781
782 def _DoCall(self, function_name, *args, **kwargs):
783 """Call the named function in the device-specific module, passing
784 the given args and kwargs. The first argument to the call will be
785 the DeviceSpecific object itself. If there is no module, or the
786 module does not define the function, return the value of the
787 'default' kwarg (which itself defaults to None)."""
788 if self.module is None or not hasattr(self.module, function_name):
789 return kwargs.get("default", None)
790 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
791
792 def FullOTA_Assertions(self):
793 """Called after emitting the block of assertions at the top of a
794 full OTA package. Implementations can add whatever additional
795 assertions they like."""
796 return self._DoCall("FullOTA_Assertions")
797
Doug Zongkere5ff5902012-01-17 10:55:37 -0800798 def FullOTA_InstallBegin(self):
799 """Called at the start of full OTA installation."""
800 return self._DoCall("FullOTA_InstallBegin")
801
Doug Zongker05d3dea2009-06-22 11:32:31 -0700802 def FullOTA_InstallEnd(self):
803 """Called at the end of full OTA installation; typically this is
804 used to install the image for the device's baseband processor."""
805 return self._DoCall("FullOTA_InstallEnd")
806
807 def IncrementalOTA_Assertions(self):
808 """Called after emitting the block of assertions at the top of an
809 incremental OTA package. Implementations can add whatever
810 additional assertions they like."""
811 return self._DoCall("IncrementalOTA_Assertions")
812
Doug Zongkere5ff5902012-01-17 10:55:37 -0800813 def IncrementalOTA_VerifyBegin(self):
814 """Called at the start of the verification phase of incremental
815 OTA installation; additional checks can be placed here to abort
816 the script before any changes are made."""
817 return self._DoCall("IncrementalOTA_VerifyBegin")
818
Doug Zongker05d3dea2009-06-22 11:32:31 -0700819 def IncrementalOTA_VerifyEnd(self):
820 """Called at the end of the verification phase of incremental OTA
821 installation; additional checks can be placed here to abort the
822 script before any changes are made."""
823 return self._DoCall("IncrementalOTA_VerifyEnd")
824
Doug Zongkere5ff5902012-01-17 10:55:37 -0800825 def IncrementalOTA_InstallBegin(self):
826 """Called at the start of incremental OTA installation (after
827 verification is complete)."""
828 return self._DoCall("IncrementalOTA_InstallBegin")
829
Doug Zongker05d3dea2009-06-22 11:32:31 -0700830 def IncrementalOTA_InstallEnd(self):
831 """Called at the end of incremental OTA installation; typically
832 this is used to install the image for the device's baseband
833 processor."""
834 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700835
836class File(object):
837 def __init__(self, name, data):
838 self.name = name
839 self.data = data
840 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800841 self.sha1 = sha1(data).hexdigest()
842
843 @classmethod
844 def FromLocalFile(cls, name, diskname):
845 f = open(diskname, "rb")
846 data = f.read()
847 f.close()
848 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700849
850 def WriteToTemp(self):
851 t = tempfile.NamedTemporaryFile()
852 t.write(self.data)
853 t.flush()
854 return t
855
Geremy Condra36bd3652014-02-06 19:45:10 -0800856 def AddToZip(self, z, compression=None):
857 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700858
859DIFF_PROGRAM_BY_EXT = {
860 ".gz" : "imgdiff",
861 ".zip" : ["imgdiff", "-z"],
862 ".jar" : ["imgdiff", "-z"],
863 ".apk" : ["imgdiff", "-z"],
864 ".img" : "imgdiff",
865 }
866
867class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700868 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700869 self.tf = tf
870 self.sf = sf
871 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700872 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700873
874 def ComputePatch(self):
875 """Compute the patch (as a string of data) needed to turn sf into
876 tf. Returns the same tuple as GetPatch()."""
877
878 tf = self.tf
879 sf = self.sf
880
Doug Zongker24cd2802012-08-14 16:36:15 -0700881 if self.diff_program:
882 diff_program = self.diff_program
883 else:
884 ext = os.path.splitext(tf.name)[1]
885 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700886
887 ttemp = tf.WriteToTemp()
888 stemp = sf.WriteToTemp()
889
890 ext = os.path.splitext(tf.name)[1]
891
892 try:
893 ptemp = tempfile.NamedTemporaryFile()
894 if isinstance(diff_program, list):
895 cmd = copy.copy(diff_program)
896 else:
897 cmd = [diff_program]
898 cmd.append(stemp.name)
899 cmd.append(ttemp.name)
900 cmd.append(ptemp.name)
901 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
902 _, err = p.communicate()
903 if err or p.returncode != 0:
904 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
905 return None
906 diff = ptemp.read()
907 finally:
908 ptemp.close()
909 stemp.close()
910 ttemp.close()
911
912 self.patch = diff
913 return self.tf, self.sf, self.patch
914
915
916 def GetPatch(self):
917 """Return a tuple (target_file, source_file, patch_data).
918 patch_data may be None if ComputePatch hasn't been called, or if
919 computing the patch failed."""
920 return self.tf, self.sf, self.patch
921
922
923def ComputeDifferences(diffs):
924 """Call ComputePatch on all the Difference objects in 'diffs'."""
925 print len(diffs), "diffs to compute"
926
927 # Do the largest files first, to try and reduce the long-pole effect.
928 by_size = [(i.tf.size, i) for i in diffs]
929 by_size.sort(reverse=True)
930 by_size = [i[1] for i in by_size]
931
932 lock = threading.Lock()
933 diff_iter = iter(by_size) # accessed under lock
934
935 def worker():
936 try:
937 lock.acquire()
938 for d in diff_iter:
939 lock.release()
940 start = time.time()
941 d.ComputePatch()
942 dur = time.time() - start
943 lock.acquire()
944
945 tf, sf, patch = d.GetPatch()
946 if sf.name == tf.name:
947 name = tf.name
948 else:
949 name = "%s (%s)" % (tf.name, sf.name)
950 if patch is None:
951 print "patching failed! %s" % (name,)
952 else:
953 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
954 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
955 lock.release()
956 except Exception, e:
957 print e
958 raise
959
960 # start worker threads; wait for them all to finish.
961 threads = [threading.Thread(target=worker)
962 for i in range(OPTIONS.worker_threads)]
963 for th in threads:
964 th.start()
965 while threads:
966 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700967
968
969# map recovery.fstab's fs_types to mount/format "partition types"
970PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
971 "ext4": "EMMC", "emmc": "EMMC" }
972
973def GetTypeAndDevice(mount_point, info):
974 fstab = info["fstab"]
975 if fstab:
976 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
977 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800978 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +0000979
980
981def ParseCertificate(data):
982 """Parse a PEM-format certificate."""
983 cert = []
984 save = False
985 for line in data.split("\n"):
986 if "--END CERTIFICATE--" in line:
987 break
988 if save:
989 cert.append(line)
990 if "--BEGIN CERTIFICATE--" in line:
991 save = True
992 cert = "".join(cert).decode('base64')
993 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -0800994
Geremy Condra36bd3652014-02-06 19:45:10 -0800995def XDelta3(source_path, target_path, output_path):
996 diff_program = ["xdelta3", "-0", "-B", str(64<<20), "-e", "-f", "-s"]
997 diff_program.append(source_path)
998 diff_program.append(target_path)
999 diff_program.append(output_path)
1000 p = Run(diff_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1001 p.communicate()
1002 assert p.returncode == 0, "Couldn't produce patch"
1003
1004def XZ(path):
1005 compress_program = ["xz", "-zk", "-9", "--check=crc32"]
1006 compress_program.append(path)
1007 p = Run(compress_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1008 p.communicate()
1009 assert p.returncode == 0, "Couldn't compress patch"
1010
1011def MakeSystemPatch(source_file, target_file):
1012 with tempfile.NamedTemporaryFile() as output_file:
1013 XDelta3(source_file.name, target_file.name, output_file.name)
1014 XZ(output_file.name)
1015 with open(output_file.name + ".xz") as patch_file:
1016 patch_data = patch_file.read()
1017 os.unlink(patch_file.name)
Doug Zongker5fad2032014-02-24 08:13:45 -08001018 return File("system.muimg.p", patch_data)
Doug Zongkerc9253822014-02-04 12:17:58 -08001019
Doug Zongker412c02f2014-02-13 10:58:24 -08001020def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1021 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001022 """Generate a binary patch that creates the recovery image starting
1023 with the boot image. (Most of the space in these images is just the
1024 kernel, which is identical for the two, so the resulting patch
1025 should be efficient.) Add it to the output zip, along with a shell
1026 script that is run from init.rc on first boot to actually do the
1027 patching and install the new recovery image.
1028
1029 recovery_img and boot_img should be File objects for the
1030 corresponding images. info should be the dictionary returned by
1031 common.LoadInfoDict() on the input target_files.
1032 """
1033
Doug Zongker412c02f2014-02-13 10:58:24 -08001034 if info_dict is None:
1035 info_dict = OPTIONS.info_dict
1036
Doug Zongkerc9253822014-02-04 12:17:58 -08001037 diff_program = ["imgdiff"]
1038 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1039 if os.path.exists(path):
1040 diff_program.append("-b")
1041 diff_program.append(path)
1042 bonus_args = "-b /system/etc/recovery-resource.dat"
1043 else:
1044 bonus_args = ""
1045
1046 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1047 _, _, patch = d.ComputePatch()
1048 output_sink("recovery-from-boot.p", patch)
1049
Doug Zongker412c02f2014-02-13 10:58:24 -08001050 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1051 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
Doug Zongkerc9253822014-02-04 12:17:58 -08001052
1053 sh = """#!/system/bin/sh
1054if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1055 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"
1056else
1057 log -t recovery "Recovery image already installed"
1058fi
1059""" % { 'boot_size': boot_img.size,
1060 'boot_sha1': boot_img.sha1,
1061 'recovery_size': recovery_img.size,
1062 'recovery_sha1': recovery_img.sha1,
1063 'boot_type': boot_type,
1064 'boot_device': boot_device,
1065 'recovery_type': recovery_type,
1066 'recovery_device': recovery_device,
1067 'bonus_args': bonus_args,
1068 }
1069
1070 # The install script location moved from /system/etc to /system/bin
1071 # in the L release. Parse the init.rc file to find out where the
1072 # target-files expects it to be, and put it there.
1073 sh_location = "etc/install-recovery.sh"
1074 try:
1075 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1076 for line in f:
1077 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1078 if m:
1079 sh_location = m.group(1)
1080 print "putting script in", sh_location
1081 break
1082 except (OSError, IOError), e:
1083 print "failed to read init.rc: %s" % (e,)
1084
1085 output_sink(sh_location, sh)