blob: 50ef451214877c44ab0a3562e54578689c785afc [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
Doug Zongker171f1cd2009-06-15 22:36:37 -0700306 fn = os.path.join(sourcedir, "cmdline")
307 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700308 cmd.append("--cmdline")
309 cmd.append(open(fn).read().rstrip("\n"))
310
311 fn = os.path.join(sourcedir, "base")
312 if os.access(fn, os.F_OK):
313 cmd.append("--base")
314 cmd.append(open(fn).read().rstrip("\n"))
315
Ying Wang4de6b5b2010-08-25 14:29:34 -0700316 fn = os.path.join(sourcedir, "pagesize")
317 if os.access(fn, os.F_OK):
318 cmd.append("--pagesize")
319 cmd.append(open(fn).read().rstrip("\n"))
320
Doug Zongkerd5131602012-08-02 14:46:42 -0700321 args = info_dict.get("mkbootimg_args", None)
322 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700323 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700324
Doug Zongker38a649f2009-06-17 09:07:09 -0700325 cmd.extend(["--ramdisk", ramdisk_img.name,
326 "--output", img.name])
327
328 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700329 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700330 assert p.returncode == 0, "mkbootimg of %s image failed" % (
331 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700332
333 img.seek(os.SEEK_SET, 0)
334 data = img.read()
335
336 ramdisk_img.close()
337 img.close()
338
339 return data
340
341
Doug Zongkerd5131602012-08-02 14:46:42 -0700342def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
343 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800344 """Return a File object (with name 'name') with the desired bootable
345 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
346 'prebuilt_name', otherwise construct it from the source files in
347 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700348
Doug Zongker55d93282011-01-25 17:03:34 -0800349 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
350 if os.path.exists(prebuilt_path):
351 print "using prebuilt %s..." % (prebuilt_name,)
352 return File.FromLocalFile(name, prebuilt_path)
353 else:
354 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700355 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Ying Wangd89ffa82014-02-05 11:28:51 -0800356 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
357 os.path.join(unpack_dir, fs_config),
Ying Wang64a55ba2014-02-05 12:15:06 -0800358 info_dict)
Ying Wangd89ffa82014-02-05 11:28:51 -0800359 if data:
360 return File(name, data)
361 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800362
Doug Zongkereef39442009-04-02 12:14:19 -0700363
Doug Zongker75f17362009-12-08 13:46:44 -0800364def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800365 """Unzip the given archive into a temporary directory and return the name.
366
367 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
368 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
369
370 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
371 main file), open for reading.
372 """
Doug Zongkereef39442009-04-02 12:14:19 -0700373
374 tmp = tempfile.mkdtemp(prefix="targetfiles-")
375 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800376
377 def unzip_to_dir(filename, dirname):
378 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
379 if pattern is not None:
380 cmd.append(pattern)
381 p = Run(cmd, stdout=subprocess.PIPE)
382 p.communicate()
383 if p.returncode != 0:
384 raise ExternalError("failed to unzip input target-files \"%s\"" %
385 (filename,))
386
387 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
388 if m:
389 unzip_to_dir(m.group(1), tmp)
390 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
391 filename = m.group(1)
392 else:
393 unzip_to_dir(filename, tmp)
394
395 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700396
397
398def GetKeyPasswords(keylist):
399 """Given a list of keys, prompt the user to enter passwords for
400 those which require them. Return a {key: password} dict. password
401 will be None if the key has no password."""
402
Doug Zongker8ce7c252009-05-22 13:34:54 -0700403 no_passwords = []
404 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700405 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700406 devnull = open("/dev/null", "w+b")
407 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800408 # We don't need a password for things that aren't really keys.
409 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700410 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700411 continue
412
T.R. Fullhart37e10522013-03-18 10:31:26 -0700413 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700414 "-inform", "DER", "-nocrypt"],
415 stdin=devnull.fileno(),
416 stdout=devnull.fileno(),
417 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700418 p.communicate()
419 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700420 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700421 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700422 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700423 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
424 "-inform", "DER", "-passin", "pass:"],
425 stdin=devnull.fileno(),
426 stdout=devnull.fileno(),
427 stderr=subprocess.PIPE)
428 stdout, stderr = p.communicate()
429 if p.returncode == 0:
430 # Encrypted key with empty string as password.
431 key_passwords[k] = ''
432 elif stderr.startswith('Error decrypting key'):
433 # Definitely encrypted key.
434 # It would have said "Error reading key" if it didn't parse correctly.
435 need_passwords.append(k)
436 else:
437 # Potentially, a type of key that openssl doesn't understand.
438 # We'll let the routines in signapk.jar handle it.
439 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700440 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700441
T.R. Fullhart37e10522013-03-18 10:31:26 -0700442 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700443 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700444 return key_passwords
445
446
Doug Zongker951495f2009-08-14 12:44:19 -0700447def SignFile(input_name, output_name, key, password, align=None,
448 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700449 """Sign the input_name zip/jar/apk, producing output_name. Use the
450 given key and password (the latter may be None if the key does not
451 have a password.
452
453 If align is an integer > 1, zipalign is run to align stored files in
454 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700455
456 If whole_file is true, use the "-w" option to SignApk to embed a
457 signature that covers the whole file in the archive comment of the
458 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700459 """
Doug Zongker951495f2009-08-14 12:44:19 -0700460
Doug Zongkereef39442009-04-02 12:14:19 -0700461 if align == 0 or align == 1:
462 align = None
463
464 if align:
465 temp = tempfile.NamedTemporaryFile()
466 sign_name = temp.name
467 else:
468 sign_name = output_name
469
T.R. Fullhart37e10522013-03-18 10:31:26 -0700470 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
471 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
472 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700473 if whole_file:
474 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700475 cmd.extend([key + OPTIONS.public_key_suffix,
476 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700477 input_name, sign_name])
478
479 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700480 if password is not None:
481 password += "\n"
482 p.communicate(password)
483 if p.returncode != 0:
484 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
485
486 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700487 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700488 p.communicate()
489 if p.returncode != 0:
490 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
491 temp.close()
492
493
Doug Zongker37974732010-09-16 17:44:38 -0700494def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700495 """Check the data string passed against the max size limit, if
496 any, for the given target. Raise exception if the data is too big.
497 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700498
Doug Zongker1684d9c2010-09-17 07:44:38 -0700499 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700500 mount_point = "/" + target
501
Ying Wangf8824af2014-06-03 14:07:27 -0700502 fs_type = None
503 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700504 if info_dict["fstab"]:
505 if mount_point == "/userdata": mount_point = "/data"
506 p = info_dict["fstab"][mount_point]
507 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800508 device = p.device
509 if "/" in device:
510 device = device[device.rfind("/")+1:]
511 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700512 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700513
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700514 if fs_type == "yaffs2":
515 # image size should be increased by 1/64th to account for the
516 # spare area (64 bytes per 2k page)
517 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800518 size = len(data)
519 pct = float(size) * 100.0 / limit
520 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
521 if pct >= 99.0:
522 raise ExternalError(msg)
523 elif pct >= 95.0:
524 print
525 print " WARNING: ", msg
526 print
527 elif OPTIONS.verbose:
528 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700529
530
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800531def ReadApkCerts(tf_zip):
532 """Given a target_files ZipFile, parse the META/apkcerts.txt file
533 and return a {package: cert} dict."""
534 certmap = {}
535 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
536 line = line.strip()
537 if not line: continue
538 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
539 r'private_key="(.*)"$', line)
540 if m:
541 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700542 public_key_suffix_len = len(OPTIONS.public_key_suffix)
543 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800544 if cert in SPECIAL_CERT_STRINGS and not privkey:
545 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700546 elif (cert.endswith(OPTIONS.public_key_suffix) and
547 privkey.endswith(OPTIONS.private_key_suffix) and
548 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
549 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800550 else:
551 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
552 return certmap
553
554
Doug Zongkereef39442009-04-02 12:14:19 -0700555COMMON_DOCSTRING = """
556 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700557 Prepend <dir>/bin to the list of places to search for binaries
558 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700559
Doug Zongker05d3dea2009-06-22 11:32:31 -0700560 -s (--device_specific) <file>
561 Path to the python module containing device-specific
562 releasetools code.
563
Doug Zongker8bec09e2009-11-30 15:37:14 -0800564 -x (--extra) <key=value>
565 Add a key/value pair to the 'extras' dict, which device-specific
566 extension code may look at.
567
Doug Zongkereef39442009-04-02 12:14:19 -0700568 -v (--verbose)
569 Show command lines being executed.
570
571 -h (--help)
572 Display this usage message and exit.
573"""
574
575def Usage(docstring):
576 print docstring.rstrip("\n")
577 print COMMON_DOCSTRING
578
579
580def ParseOptions(argv,
581 docstring,
582 extra_opts="", extra_long_opts=(),
583 extra_option_handler=None):
584 """Parse the options in argv and return any arguments that aren't
585 flags. docstring is the calling module's docstring, to be displayed
586 for errors and -h. extra_opts and extra_long_opts are for flags
587 defined by the caller, which are processed by passing them to
588 extra_option_handler."""
589
590 try:
591 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800592 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700593 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
594 "java_path=", "public_key_suffix=", "private_key_suffix=",
595 "device_specific=", "extra="] +
596 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700597 except getopt.GetoptError, err:
598 Usage(docstring)
599 print "**", str(err), "**"
600 sys.exit(2)
601
602 path_specified = False
603
604 for o, a in opts:
605 if o in ("-h", "--help"):
606 Usage(docstring)
607 sys.exit()
608 elif o in ("-v", "--verbose"):
609 OPTIONS.verbose = True
610 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700611 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 elif o in ("--signapk_path",):
613 OPTIONS.signapk_path = a
614 elif o in ("--extra_signapk_args",):
615 OPTIONS.extra_signapk_args = shlex.split(a)
616 elif o in ("--java_path",):
617 OPTIONS.java_path = a
618 elif o in ("--public_key_suffix",):
619 OPTIONS.public_key_suffix = a
620 elif o in ("--private_key_suffix",):
621 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700622 elif o in ("-s", "--device_specific"):
623 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800624 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800625 key, value = a.split("=", 1)
626 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700627 else:
628 if extra_option_handler is None or not extra_option_handler(o, a):
629 assert False, "unknown option \"%s\"" % (o,)
630
Doug Zongker602a84e2009-06-18 08:35:12 -0700631 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
632 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700633
634 return args
635
636
637def Cleanup():
638 for i in OPTIONS.tempfiles:
639 if os.path.isdir(i):
640 shutil.rmtree(i)
641 else:
642 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700643
644
645class PasswordManager(object):
646 def __init__(self):
647 self.editor = os.getenv("EDITOR", None)
648 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
649
650 def GetPasswords(self, items):
651 """Get passwords corresponding to each string in 'items',
652 returning a dict. (The dict may have keys in addition to the
653 values in 'items'.)
654
655 Uses the passwords in $ANDROID_PW_FILE if available, letting the
656 user edit that file to add more needed passwords. If no editor is
657 available, or $ANDROID_PW_FILE isn't define, prompts the user
658 interactively in the ordinary way.
659 """
660
661 current = self.ReadFile()
662
663 first = True
664 while True:
665 missing = []
666 for i in items:
667 if i not in current or not current[i]:
668 missing.append(i)
669 # Are all the passwords already in the file?
670 if not missing: return current
671
672 for i in missing:
673 current[i] = ""
674
675 if not first:
676 print "key file %s still missing some passwords." % (self.pwfile,)
677 answer = raw_input("try to edit again? [y]> ").strip()
678 if answer and answer[0] not in 'yY':
679 raise RuntimeError("key passwords unavailable")
680 first = False
681
682 current = self.UpdateAndReadFile(current)
683
684 def PromptResult(self, current):
685 """Prompt the user to enter a value (password) for each key in
686 'current' whose value is fales. Returns a new dict with all the
687 values.
688 """
689 result = {}
690 for k, v in sorted(current.iteritems()):
691 if v:
692 result[k] = v
693 else:
694 while True:
695 result[k] = getpass.getpass("Enter password for %s key> "
696 % (k,)).strip()
697 if result[k]: break
698 return result
699
700 def UpdateAndReadFile(self, current):
701 if not self.editor or not self.pwfile:
702 return self.PromptResult(current)
703
704 f = open(self.pwfile, "w")
705 os.chmod(self.pwfile, 0600)
706 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
707 f.write("# (Additional spaces are harmless.)\n\n")
708
709 first_line = None
710 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
711 sorted.sort()
712 for i, (_, k, v) in enumerate(sorted):
713 f.write("[[[ %s ]]] %s\n" % (v, k))
714 if not v and first_line is None:
715 # position cursor on first line with no password.
716 first_line = i + 4
717 f.close()
718
719 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
720 _, _ = p.communicate()
721
722 return self.ReadFile()
723
724 def ReadFile(self):
725 result = {}
726 if self.pwfile is None: return result
727 try:
728 f = open(self.pwfile, "r")
729 for line in f:
730 line = line.strip()
731 if not line or line[0] == '#': continue
732 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
733 if not m:
734 print "failed to parse password file: ", line
735 else:
736 result[m.group(2)] = m.group(1)
737 f.close()
738 except IOError, e:
739 if e.errno != errno.ENOENT:
740 print "error reading password file: ", str(e)
741 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700742
743
Geremy Condra36bd3652014-02-06 19:45:10 -0800744def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700745 # use a fixed timestamp so the output is repeatable.
746 zinfo = zipfile.ZipInfo(filename=filename,
747 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800748 if compression is None:
749 zinfo.compress_type = zip.compression
750 else:
751 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700752 zinfo.external_attr = perms << 16
753 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700754
755
756class DeviceSpecificParams(object):
757 module = None
758 def __init__(self, **kwargs):
759 """Keyword arguments to the constructor become attributes of this
760 object, which is passed to all functions in the device-specific
761 module."""
762 for k, v in kwargs.iteritems():
763 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800764 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700765
766 if self.module is None:
767 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700768 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700769 try:
770 if os.path.isdir(path):
771 info = imp.find_module("releasetools", [path])
772 else:
773 d, f = os.path.split(path)
774 b, x = os.path.splitext(f)
775 if x == ".py":
776 f = b
777 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800778 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700779 self.module = imp.load_module("device_specific", *info)
780 except ImportError:
781 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700782
783 def _DoCall(self, function_name, *args, **kwargs):
784 """Call the named function in the device-specific module, passing
785 the given args and kwargs. The first argument to the call will be
786 the DeviceSpecific object itself. If there is no module, or the
787 module does not define the function, return the value of the
788 'default' kwarg (which itself defaults to None)."""
789 if self.module is None or not hasattr(self.module, function_name):
790 return kwargs.get("default", None)
791 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
792
793 def FullOTA_Assertions(self):
794 """Called after emitting the block of assertions at the top of a
795 full OTA package. Implementations can add whatever additional
796 assertions they like."""
797 return self._DoCall("FullOTA_Assertions")
798
Doug Zongkere5ff5902012-01-17 10:55:37 -0800799 def FullOTA_InstallBegin(self):
800 """Called at the start of full OTA installation."""
801 return self._DoCall("FullOTA_InstallBegin")
802
Doug Zongker05d3dea2009-06-22 11:32:31 -0700803 def FullOTA_InstallEnd(self):
804 """Called at the end of full OTA installation; typically this is
805 used to install the image for the device's baseband processor."""
806 return self._DoCall("FullOTA_InstallEnd")
807
808 def IncrementalOTA_Assertions(self):
809 """Called after emitting the block of assertions at the top of an
810 incremental OTA package. Implementations can add whatever
811 additional assertions they like."""
812 return self._DoCall("IncrementalOTA_Assertions")
813
Doug Zongkere5ff5902012-01-17 10:55:37 -0800814 def IncrementalOTA_VerifyBegin(self):
815 """Called at the start of the verification phase of incremental
816 OTA installation; additional checks can be placed here to abort
817 the script before any changes are made."""
818 return self._DoCall("IncrementalOTA_VerifyBegin")
819
Doug Zongker05d3dea2009-06-22 11:32:31 -0700820 def IncrementalOTA_VerifyEnd(self):
821 """Called at the end of the verification phase of incremental OTA
822 installation; additional checks can be placed here to abort the
823 script before any changes are made."""
824 return self._DoCall("IncrementalOTA_VerifyEnd")
825
Doug Zongkere5ff5902012-01-17 10:55:37 -0800826 def IncrementalOTA_InstallBegin(self):
827 """Called at the start of incremental OTA installation (after
828 verification is complete)."""
829 return self._DoCall("IncrementalOTA_InstallBegin")
830
Doug Zongker05d3dea2009-06-22 11:32:31 -0700831 def IncrementalOTA_InstallEnd(self):
832 """Called at the end of incremental OTA installation; typically
833 this is used to install the image for the device's baseband
834 processor."""
835 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700836
837class File(object):
838 def __init__(self, name, data):
839 self.name = name
840 self.data = data
841 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800842 self.sha1 = sha1(data).hexdigest()
843
844 @classmethod
845 def FromLocalFile(cls, name, diskname):
846 f = open(diskname, "rb")
847 data = f.read()
848 f.close()
849 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700850
851 def WriteToTemp(self):
852 t = tempfile.NamedTemporaryFile()
853 t.write(self.data)
854 t.flush()
855 return t
856
Geremy Condra36bd3652014-02-06 19:45:10 -0800857 def AddToZip(self, z, compression=None):
858 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700859
860DIFF_PROGRAM_BY_EXT = {
861 ".gz" : "imgdiff",
862 ".zip" : ["imgdiff", "-z"],
863 ".jar" : ["imgdiff", "-z"],
864 ".apk" : ["imgdiff", "-z"],
865 ".img" : "imgdiff",
866 }
867
868class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700869 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700870 self.tf = tf
871 self.sf = sf
872 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700873 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700874
875 def ComputePatch(self):
876 """Compute the patch (as a string of data) needed to turn sf into
877 tf. Returns the same tuple as GetPatch()."""
878
879 tf = self.tf
880 sf = self.sf
881
Doug Zongker24cd2802012-08-14 16:36:15 -0700882 if self.diff_program:
883 diff_program = self.diff_program
884 else:
885 ext = os.path.splitext(tf.name)[1]
886 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700887
888 ttemp = tf.WriteToTemp()
889 stemp = sf.WriteToTemp()
890
891 ext = os.path.splitext(tf.name)[1]
892
893 try:
894 ptemp = tempfile.NamedTemporaryFile()
895 if isinstance(diff_program, list):
896 cmd = copy.copy(diff_program)
897 else:
898 cmd = [diff_program]
899 cmd.append(stemp.name)
900 cmd.append(ttemp.name)
901 cmd.append(ptemp.name)
902 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700903 err = []
904 def run():
905 _, e = p.communicate()
906 if e: err.append(e)
907 th = threading.Thread(target=run)
908 th.start()
909 th.join(timeout=300) # 5 mins
910 if th.is_alive():
911 print "WARNING: diff command timed out"
912 p.terminate()
913 th.join(5)
914 if th.is_alive():
915 p.kill()
916 th.join()
917
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700918 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700919 print "WARNING: failure running %s:\n%s\n" % (
920 diff_program, "".join(err))
921 self.patch = None
922 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700923 diff = ptemp.read()
924 finally:
925 ptemp.close()
926 stemp.close()
927 ttemp.close()
928
929 self.patch = diff
930 return self.tf, self.sf, self.patch
931
932
933 def GetPatch(self):
934 """Return a tuple (target_file, source_file, patch_data).
935 patch_data may be None if ComputePatch hasn't been called, or if
936 computing the patch failed."""
937 return self.tf, self.sf, self.patch
938
939
940def ComputeDifferences(diffs):
941 """Call ComputePatch on all the Difference objects in 'diffs'."""
942 print len(diffs), "diffs to compute"
943
944 # Do the largest files first, to try and reduce the long-pole effect.
945 by_size = [(i.tf.size, i) for i in diffs]
946 by_size.sort(reverse=True)
947 by_size = [i[1] for i in by_size]
948
949 lock = threading.Lock()
950 diff_iter = iter(by_size) # accessed under lock
951
952 def worker():
953 try:
954 lock.acquire()
955 for d in diff_iter:
956 lock.release()
957 start = time.time()
958 d.ComputePatch()
959 dur = time.time() - start
960 lock.acquire()
961
962 tf, sf, patch = d.GetPatch()
963 if sf.name == tf.name:
964 name = tf.name
965 else:
966 name = "%s (%s)" % (tf.name, sf.name)
967 if patch is None:
968 print "patching failed! %s" % (name,)
969 else:
970 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
971 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
972 lock.release()
973 except Exception, e:
974 print e
975 raise
976
977 # start worker threads; wait for them all to finish.
978 threads = [threading.Thread(target=worker)
979 for i in range(OPTIONS.worker_threads)]
980 for th in threads:
981 th.start()
982 while threads:
983 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700984
985
986# map recovery.fstab's fs_types to mount/format "partition types"
987PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -0700988 "ext4": "EMMC", "emmc": "EMMC",
989 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -0700990
991def GetTypeAndDevice(mount_point, info):
992 fstab = info["fstab"]
993 if fstab:
994 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
995 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800996 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +0000997
998
999def ParseCertificate(data):
1000 """Parse a PEM-format certificate."""
1001 cert = []
1002 save = False
1003 for line in data.split("\n"):
1004 if "--END CERTIFICATE--" in line:
1005 break
1006 if save:
1007 cert.append(line)
1008 if "--BEGIN CERTIFICATE--" in line:
1009 save = True
1010 cert = "".join(cert).decode('base64')
1011 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001012
Geremy Condra36bd3652014-02-06 19:45:10 -08001013def XDelta3(source_path, target_path, output_path):
1014 diff_program = ["xdelta3", "-0", "-B", str(64<<20), "-e", "-f", "-s"]
1015 diff_program.append(source_path)
1016 diff_program.append(target_path)
1017 diff_program.append(output_path)
1018 p = Run(diff_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1019 p.communicate()
1020 assert p.returncode == 0, "Couldn't produce patch"
1021
1022def XZ(path):
1023 compress_program = ["xz", "-zk", "-9", "--check=crc32"]
1024 compress_program.append(path)
1025 p = Run(compress_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1026 p.communicate()
1027 assert p.returncode == 0, "Couldn't compress patch"
1028
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001029def MakePartitionPatch(source_file, target_file, partition):
Geremy Condra36bd3652014-02-06 19:45:10 -08001030 with tempfile.NamedTemporaryFile() as output_file:
1031 XDelta3(source_file.name, target_file.name, output_file.name)
1032 XZ(output_file.name)
1033 with open(output_file.name + ".xz") as patch_file:
1034 patch_data = patch_file.read()
1035 os.unlink(patch_file.name)
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001036 return File(partition + ".muimg.p", patch_data)
Doug Zongkerc9253822014-02-04 12:17:58 -08001037
Doug Zongker412c02f2014-02-13 10:58:24 -08001038def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1039 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001040 """Generate a binary patch that creates the recovery image starting
1041 with the boot image. (Most of the space in these images is just the
1042 kernel, which is identical for the two, so the resulting patch
1043 should be efficient.) Add it to the output zip, along with a shell
1044 script that is run from init.rc on first boot to actually do the
1045 patching and install the new recovery image.
1046
1047 recovery_img and boot_img should be File objects for the
1048 corresponding images. info should be the dictionary returned by
1049 common.LoadInfoDict() on the input target_files.
1050 """
1051
Doug Zongker412c02f2014-02-13 10:58:24 -08001052 if info_dict is None:
1053 info_dict = OPTIONS.info_dict
1054
Doug Zongkerc9253822014-02-04 12:17:58 -08001055 diff_program = ["imgdiff"]
1056 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1057 if os.path.exists(path):
1058 diff_program.append("-b")
1059 diff_program.append(path)
1060 bonus_args = "-b /system/etc/recovery-resource.dat"
1061 else:
1062 bonus_args = ""
1063
1064 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1065 _, _, patch = d.ComputePatch()
1066 output_sink("recovery-from-boot.p", patch)
1067
Ying Wanga961a092014-07-29 11:42:37 -07001068 td_pair = GetTypeAndDevice("/boot", info_dict)
1069 if not td_pair:
1070 return
1071 boot_type, boot_device = td_pair
1072 td_pair = GetTypeAndDevice("/recovery", info_dict)
1073 if not td_pair:
1074 return
1075 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001076
1077 sh = """#!/system/bin/sh
1078if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1079 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"
1080else
1081 log -t recovery "Recovery image already installed"
1082fi
1083""" % { 'boot_size': boot_img.size,
1084 'boot_sha1': boot_img.sha1,
1085 'recovery_size': recovery_img.size,
1086 'recovery_sha1': recovery_img.sha1,
1087 'boot_type': boot_type,
1088 'boot_device': boot_device,
1089 'recovery_type': recovery_type,
1090 'recovery_device': recovery_device,
1091 'bonus_args': bonus_args,
1092 }
1093
1094 # The install script location moved from /system/etc to /system/bin
1095 # in the L release. Parse the init.rc file to find out where the
1096 # target-files expects it to be, and put it there.
1097 sh_location = "etc/install-recovery.sh"
1098 try:
1099 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1100 for line in f:
1101 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1102 if m:
1103 sh_location = m.group(1)
1104 print "putting script in", sh_location
1105 break
1106 except (OSError, IOError), e:
1107 print "failed to read init.rc: %s" % (e,)
1108
1109 output_sink(sh_location, sh)