blob: 682dc45bf0e1dbd53c3c4787c7e0c17286c0baa5 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Bao2ed665a2015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Baligh Uddin852a5b52014-11-20 09:52:05 -080052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.verbose = False
55 self.tempfiles = []
56 self.device_specific = None
57 self.extras = {}
58 self.info_dict = None
59 self.worker_threads = None
60
61
62OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070063
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080064
65# Values for "certificate" in apkcerts that mean special things.
66SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
67
68
Dan Albert8b72aef2015-03-23 19:13:21 -070069class ExternalError(RuntimeError):
70 pass
Doug Zongkereef39442009-04-02 12:14:19 -070071
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Dan Albert8b72aef2015-03-23 19:13:21 -070097def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700102 if isinstance(input_file, zipfile.ZipFile):
103 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800106 try:
107 with open(path) as f:
108 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700109 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700125 d["mkyaffs2_extra_flags"] = read_helper(
126 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700127 except KeyError:
128 # ok if flags don't exist
129 pass
130
131 if "recovery_api_version" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["recovery_api_version"] = read_helper(
134 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 raise ValueError("can't find recovery API version in input target-files")
137
138 if "tool_extensions" not in d:
139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 # ok if extensions don't exist
143 pass
144
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800145 if "fstab_version" not in d:
146 d["fstab_version"] = "1"
147
Doug Zongker37974732010-09-16 17:44:38 -0700148 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700150 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700151 if not line:
152 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700153 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not value:
155 continue
Doug Zongker37974732010-09-16 17:44:38 -0700156 if name == "blocksize":
157 d[name] = value
158 else:
159 d[name + "_size"] = value
160 except KeyError:
161 pass
162
163 def makeint(key):
164 if key in d:
165 d[key] = int(d[key], 0)
166
167 makeint("recovery_api_version")
168 makeint("blocksize")
169 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700170 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700171 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700172 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700173 makeint("recovery_size")
174 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700176
Doug Zongkerc9253822014-02-04 12:17:58 -0800177 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
178 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700179 return d
180
Doug Zongkerc9253822014-02-04 12:17:58 -0800181def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800183 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184 except KeyError:
185 print "Warning: could not find SYSTEM/build.prop in %s" % zip
186 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700187 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188
Michael Runge6e836112014-04-15 17:40:21 -0700189def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700190 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700191 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700192 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700193 if not line or line.startswith("#"):
194 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700195 if "=" in line:
196 name, value = line.split("=", 1)
197 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700198 return d
199
Doug Zongkerc9253822014-02-04 12:17:58 -0800200def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700201 class Partition(object):
Dan Albert8b72aef2015-03-23 19:13:21 -0700202 def __init__(self, mount_point, fs_type, device, length, device2):
203 self.mount_point = mount_point
204 self.fs_type = fs_type
205 self.device = device
206 self.length = length
207 self.device2 = device2
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700208
209 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800210 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700211 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800212 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700213 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800215 if fstab_version == 1:
216 d = {}
217 for line in data.split("\n"):
218 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700219 if not line or line.startswith("#"):
220 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800221 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700222 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800223 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 options = None
225 if len(pieces) >= 4:
226 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700227 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800228 if len(pieces) >= 5:
229 options = pieces[4]
230 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800233 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700235
Dan Albert8b72aef2015-03-23 19:13:21 -0700236 mount_point = pieces[0]
237 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 if options:
239 options = options.split(",")
240 for i in options:
241 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700242 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800243 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800245
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
247 device=pieces[2], length=length,
248 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249
250 elif fstab_version == 2:
251 d = {}
252 for line in data.split("\n"):
253 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 if not line or line.startswith("#"):
255 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 pieces = line.split()
257 if len(pieces) != 5:
258 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
259
260 # Ignore entries that are managed by vold
261 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700262 if "voldmanaged=" in options:
263 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264
265 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800267 options = options.split(",")
268 for i in options:
269 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800271 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800272 # Ignore all unknown options in the unified fstab
273 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800274
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 mount_point = pieces[1]
276 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
277 device=pieces[0], length=length, device2=None)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800278
279 else:
280 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
281
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700282 return d
283
284
Doug Zongker37974732010-09-16 17:44:38 -0700285def DumpInfoDict(d):
286 for k, v in sorted(d.items()):
287 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700288
Dan Albert8b72aef2015-03-23 19:13:21 -0700289
Doug Zongkerd5131602012-08-02 14:46:42 -0700290def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700291 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700292 'sourcedir'), and turn them into a boot image. Return the image
293 data, or None if sourcedir does not appear to contains files for
294 building the requested image."""
295
296 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
297 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
298 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700299
Doug Zongkerd5131602012-08-02 14:46:42 -0700300 if info_dict is None:
301 info_dict = OPTIONS.info_dict
302
Doug Zongkereef39442009-04-02 12:14:19 -0700303 ramdisk_img = tempfile.NamedTemporaryFile()
304 img = tempfile.NamedTemporaryFile()
305
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700306 if os.access(fs_config_file, os.F_OK):
307 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
308 else:
309 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
310 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700311 p2 = Run(["minigzip"],
312 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700313
314 p2.wait()
315 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
317 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700318
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800319 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
320 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
321
322 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700323
Benoit Fradina45a8682014-07-14 21:00:43 +0200324 fn = os.path.join(sourcedir, "second")
325 if os.access(fn, os.F_OK):
326 cmd.append("--second")
327 cmd.append(fn)
328
Doug Zongker171f1cd2009-06-15 22:36:37 -0700329 fn = os.path.join(sourcedir, "cmdline")
330 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700331 cmd.append("--cmdline")
332 cmd.append(open(fn).read().rstrip("\n"))
333
334 fn = os.path.join(sourcedir, "base")
335 if os.access(fn, os.F_OK):
336 cmd.append("--base")
337 cmd.append(open(fn).read().rstrip("\n"))
338
Ying Wang4de6b5b2010-08-25 14:29:34 -0700339 fn = os.path.join(sourcedir, "pagesize")
340 if os.access(fn, os.F_OK):
341 cmd.append("--pagesize")
342 cmd.append(open(fn).read().rstrip("\n"))
343
Doug Zongkerd5131602012-08-02 14:46:42 -0700344 args = info_dict.get("mkbootimg_args", None)
345 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700346 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700347
Tao Baod95e9fd2015-03-29 23:07:41 -0700348 img_unsigned = None
349 if info_dict.get("vboot", None):
350 img_unsigned = tempfile.NamedTemporaryFile()
351 cmd.extend(["--ramdisk", ramdisk_img.name,
352 "--output", img_unsigned.name])
353 else:
354 cmd.extend(["--ramdisk", ramdisk_img.name,
355 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700356
357 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700358 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700359 assert p.returncode == 0, "mkbootimg of %s image failed" % (
360 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700361
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700362 if info_dict.get("verity_key", None):
363 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800364 cmd = [OPTIONS.boot_signer_path, path, img.name,
365 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700366 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700367 p = Run(cmd, stdout=subprocess.PIPE)
368 p.communicate()
369 assert p.returncode == 0, "boot_signer of %s image failed" % path
370
Tao Baod95e9fd2015-03-29 23:07:41 -0700371 # Sign the image if vboot is non-empty.
372 elif info_dict.get("vboot", None):
373 path = "/" + os.path.basename(sourcedir).lower()
374 img_keyblock = tempfile.NamedTemporaryFile()
375 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
376 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
377 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
378 img.name]
379 p = Run(cmd, stdout=subprocess.PIPE)
380 p.communicate()
381 assert p.returncode == 0, "vboot_signer of %s image failed" % path
382
Tao Bao2ed665a2015-04-01 11:21:55 -0700383 # Clean up the temp files.
384 img_unsigned.close()
385 img_keyblock.close()
386
Doug Zongkereef39442009-04-02 12:14:19 -0700387 img.seek(os.SEEK_SET, 0)
388 data = img.read()
389
390 ramdisk_img.close()
391 img.close()
392
393 return data
394
395
Doug Zongkerd5131602012-08-02 14:46:42 -0700396def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
397 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800398 """Return a File object (with name 'name') with the desired bootable
399 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700400 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
401 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800402 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700403
Doug Zongker55d93282011-01-25 17:03:34 -0800404 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
405 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700406 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800407 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700408
409 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
410 if os.path.exists(prebuilt_path):
411 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
412 return File.FromLocalFile(name, prebuilt_path)
413
414 print "building image from target_files %s..." % (tree_subdir,)
415 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
416 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
417 os.path.join(unpack_dir, fs_config),
418 info_dict)
419 if data:
420 return File(name, data)
421 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800422
Doug Zongkereef39442009-04-02 12:14:19 -0700423
Doug Zongker75f17362009-12-08 13:46:44 -0800424def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800425 """Unzip the given archive into a temporary directory and return the name.
426
427 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
428 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
429
430 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
431 main file), open for reading.
432 """
Doug Zongkereef39442009-04-02 12:14:19 -0700433
434 tmp = tempfile.mkdtemp(prefix="targetfiles-")
435 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800436
437 def unzip_to_dir(filename, dirname):
438 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
439 if pattern is not None:
440 cmd.append(pattern)
441 p = Run(cmd, stdout=subprocess.PIPE)
442 p.communicate()
443 if p.returncode != 0:
444 raise ExternalError("failed to unzip input target-files \"%s\"" %
445 (filename,))
446
447 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
448 if m:
449 unzip_to_dir(m.group(1), tmp)
450 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
451 filename = m.group(1)
452 else:
453 unzip_to_dir(filename, tmp)
454
455 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700456
457
458def GetKeyPasswords(keylist):
459 """Given a list of keys, prompt the user to enter passwords for
460 those which require them. Return a {key: password} dict. password
461 will be None if the key has no password."""
462
Doug Zongker8ce7c252009-05-22 13:34:54 -0700463 no_passwords = []
464 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700465 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700466 devnull = open("/dev/null", "w+b")
467 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800468 # We don't need a password for things that aren't really keys.
469 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700470 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700471 continue
472
T.R. Fullhart37e10522013-03-18 10:31:26 -0700473 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700474 "-inform", "DER", "-nocrypt"],
475 stdin=devnull.fileno(),
476 stdout=devnull.fileno(),
477 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700478 p.communicate()
479 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700480 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700481 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700482 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700483 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
484 "-inform", "DER", "-passin", "pass:"],
485 stdin=devnull.fileno(),
486 stdout=devnull.fileno(),
487 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700488 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700489 if p.returncode == 0:
490 # Encrypted key with empty string as password.
491 key_passwords[k] = ''
492 elif stderr.startswith('Error decrypting key'):
493 # Definitely encrypted key.
494 # It would have said "Error reading key" if it didn't parse correctly.
495 need_passwords.append(k)
496 else:
497 # Potentially, a type of key that openssl doesn't understand.
498 # We'll let the routines in signapk.jar handle it.
499 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700500 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700501
T.R. Fullhart37e10522013-03-18 10:31:26 -0700502 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700503 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700504 return key_passwords
505
506
Doug Zongker951495f2009-08-14 12:44:19 -0700507def SignFile(input_name, output_name, key, password, align=None,
508 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700509 """Sign the input_name zip/jar/apk, producing output_name. Use the
510 given key and password (the latter may be None if the key does not
511 have a password.
512
513 If align is an integer > 1, zipalign is run to align stored files in
514 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700515
516 If whole_file is true, use the "-w" option to SignApk to embed a
517 signature that covers the whole file in the archive comment of the
518 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700519 """
Doug Zongker951495f2009-08-14 12:44:19 -0700520
Doug Zongkereef39442009-04-02 12:14:19 -0700521 if align == 0 or align == 1:
522 align = None
523
524 if align:
525 temp = tempfile.NamedTemporaryFile()
526 sign_name = temp.name
527 else:
528 sign_name = output_name
529
Baligh Uddin339ee492014-09-05 11:18:07 -0700530 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700531 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
532 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700533 if whole_file:
534 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700535 cmd.extend([key + OPTIONS.public_key_suffix,
536 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700537 input_name, sign_name])
538
539 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700540 if password is not None:
541 password += "\n"
542 p.communicate(password)
543 if p.returncode != 0:
544 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
545
546 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700547 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700548 p.communicate()
549 if p.returncode != 0:
550 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
551 temp.close()
552
553
Doug Zongker37974732010-09-16 17:44:38 -0700554def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700555 """Check the data string passed against the max size limit, if
556 any, for the given target. Raise exception if the data is too big.
557 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700558
Dan Albert8b72aef2015-03-23 19:13:21 -0700559 if target.endswith(".img"):
560 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700561 mount_point = "/" + target
562
Ying Wangf8824af2014-06-03 14:07:27 -0700563 fs_type = None
564 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700565 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700566 if mount_point == "/userdata":
567 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700568 p = info_dict["fstab"][mount_point]
569 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800570 device = p.device
571 if "/" in device:
572 device = device[device.rfind("/")+1:]
573 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700574 if not fs_type or not limit:
575 return
Doug Zongkereef39442009-04-02 12:14:19 -0700576
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700577 if fs_type == "yaffs2":
578 # image size should be increased by 1/64th to account for the
579 # spare area (64 bytes per 2k page)
580 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800581 size = len(data)
582 pct = float(size) * 100.0 / limit
583 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
584 if pct >= 99.0:
585 raise ExternalError(msg)
586 elif pct >= 95.0:
587 print
588 print " WARNING: ", msg
589 print
590 elif OPTIONS.verbose:
591 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700592
593
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800594def ReadApkCerts(tf_zip):
595 """Given a target_files ZipFile, parse the META/apkcerts.txt file
596 and return a {package: cert} dict."""
597 certmap = {}
598 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
599 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700600 if not line:
601 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800602 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
603 r'private_key="(.*)"$', line)
604 if m:
605 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700606 public_key_suffix_len = len(OPTIONS.public_key_suffix)
607 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800608 if cert in SPECIAL_CERT_STRINGS and not privkey:
609 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700610 elif (cert.endswith(OPTIONS.public_key_suffix) and
611 privkey.endswith(OPTIONS.private_key_suffix) and
612 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
613 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800614 else:
615 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
616 return certmap
617
618
Doug Zongkereef39442009-04-02 12:14:19 -0700619COMMON_DOCSTRING = """
620 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700621 Prepend <dir>/bin to the list of places to search for binaries
622 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700623
Doug Zongker05d3dea2009-06-22 11:32:31 -0700624 -s (--device_specific) <file>
625 Path to the python module containing device-specific
626 releasetools code.
627
Doug Zongker8bec09e2009-11-30 15:37:14 -0800628 -x (--extra) <key=value>
629 Add a key/value pair to the 'extras' dict, which device-specific
630 extension code may look at.
631
Doug Zongkereef39442009-04-02 12:14:19 -0700632 -v (--verbose)
633 Show command lines being executed.
634
635 -h (--help)
636 Display this usage message and exit.
637"""
638
639def Usage(docstring):
640 print docstring.rstrip("\n")
641 print COMMON_DOCSTRING
642
643
644def ParseOptions(argv,
645 docstring,
646 extra_opts="", extra_long_opts=(),
647 extra_option_handler=None):
648 """Parse the options in argv and return any arguments that aren't
649 flags. docstring is the calling module's docstring, to be displayed
650 for errors and -h. extra_opts and extra_long_opts are for flags
651 defined by the caller, which are processed by passing them to
652 extra_option_handler."""
653
654 try:
655 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800656 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700657 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700658 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800659 "private_key_suffix=", "boot_signer_path=", "device_specific=",
660 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700661 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700662 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700663 Usage(docstring)
664 print "**", str(err), "**"
665 sys.exit(2)
666
Doug Zongkereef39442009-04-02 12:14:19 -0700667 for o, a in opts:
668 if o in ("-h", "--help"):
669 Usage(docstring)
670 sys.exit()
671 elif o in ("-v", "--verbose"):
672 OPTIONS.verbose = True
673 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700674 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700675 elif o in ("--signapk_path",):
676 OPTIONS.signapk_path = a
677 elif o in ("--extra_signapk_args",):
678 OPTIONS.extra_signapk_args = shlex.split(a)
679 elif o in ("--java_path",):
680 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700681 elif o in ("--java_args",):
682 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700683 elif o in ("--public_key_suffix",):
684 OPTIONS.public_key_suffix = a
685 elif o in ("--private_key_suffix",):
686 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800687 elif o in ("--boot_signer_path",):
688 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700689 elif o in ("-s", "--device_specific"):
690 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800691 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800692 key, value = a.split("=", 1)
693 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700694 else:
695 if extra_option_handler is None or not extra_option_handler(o, a):
696 assert False, "unknown option \"%s\"" % (o,)
697
Doug Zongker85448772014-09-09 14:59:20 -0700698 if OPTIONS.search_path:
699 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
700 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700701
702 return args
703
704
Doug Zongkerfc44a512014-08-26 13:10:25 -0700705def MakeTempFile(prefix=None, suffix=None):
706 """Make a temp file and add it to the list of things to be deleted
707 when Cleanup() is called. Return the filename."""
708 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
709 os.close(fd)
710 OPTIONS.tempfiles.append(fn)
711 return fn
712
713
Doug Zongkereef39442009-04-02 12:14:19 -0700714def Cleanup():
715 for i in OPTIONS.tempfiles:
716 if os.path.isdir(i):
717 shutil.rmtree(i)
718 else:
719 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700720
721
722class PasswordManager(object):
723 def __init__(self):
724 self.editor = os.getenv("EDITOR", None)
725 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
726
727 def GetPasswords(self, items):
728 """Get passwords corresponding to each string in 'items',
729 returning a dict. (The dict may have keys in addition to the
730 values in 'items'.)
731
732 Uses the passwords in $ANDROID_PW_FILE if available, letting the
733 user edit that file to add more needed passwords. If no editor is
734 available, or $ANDROID_PW_FILE isn't define, prompts the user
735 interactively in the ordinary way.
736 """
737
738 current = self.ReadFile()
739
740 first = True
741 while True:
742 missing = []
743 for i in items:
744 if i not in current or not current[i]:
745 missing.append(i)
746 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700747 if not missing:
748 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700749
750 for i in missing:
751 current[i] = ""
752
753 if not first:
754 print "key file %s still missing some passwords." % (self.pwfile,)
755 answer = raw_input("try to edit again? [y]> ").strip()
756 if answer and answer[0] not in 'yY':
757 raise RuntimeError("key passwords unavailable")
758 first = False
759
760 current = self.UpdateAndReadFile(current)
761
Dan Albert8b72aef2015-03-23 19:13:21 -0700762 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700763 """Prompt the user to enter a value (password) for each key in
764 'current' whose value is fales. Returns a new dict with all the
765 values.
766 """
767 result = {}
768 for k, v in sorted(current.iteritems()):
769 if v:
770 result[k] = v
771 else:
772 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700773 result[k] = getpass.getpass(
774 "Enter password for %s key> " % k).strip()
775 if result[k]:
776 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700777 return result
778
779 def UpdateAndReadFile(self, current):
780 if not self.editor or not self.pwfile:
781 return self.PromptResult(current)
782
783 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700784 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700785 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
786 f.write("# (Additional spaces are harmless.)\n\n")
787
788 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700789 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
790 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700791 f.write("[[[ %s ]]] %s\n" % (v, k))
792 if not v and first_line is None:
793 # position cursor on first line with no password.
794 first_line = i + 4
795 f.close()
796
797 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
798 _, _ = p.communicate()
799
800 return self.ReadFile()
801
802 def ReadFile(self):
803 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700804 if self.pwfile is None:
805 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700806 try:
807 f = open(self.pwfile, "r")
808 for line in f:
809 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700810 if not line or line[0] == '#':
811 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
813 if not m:
814 print "failed to parse password file: ", line
815 else:
816 result[m.group(2)] = m.group(1)
817 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700818 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700819 if e.errno != errno.ENOENT:
820 print "error reading password file: ", str(e)
821 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700822
823
Dan Albert8e0178d2015-01-27 15:53:15 -0800824def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
825 compress_type=None):
826 import datetime
827
828 # http://b/18015246
829 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
830 # for files larger than 2GiB. We can work around this by adjusting their
831 # limit. Note that `zipfile.writestr()` will not work for strings larger than
832 # 2GiB. The Python interpreter sometimes rejects strings that large (though
833 # it isn't clear to me exactly what circumstances cause this).
834 # `zipfile.write()` must be used directly to work around this.
835 #
836 # This mess can be avoided if we port to python3.
837 saved_zip64_limit = zipfile.ZIP64_LIMIT
838 zipfile.ZIP64_LIMIT = (1 << 32) - 1
839
840 if compress_type is None:
841 compress_type = zip_file.compression
842 if arcname is None:
843 arcname = filename
844
845 saved_stat = os.stat(filename)
846
847 try:
848 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
849 # file to be zipped and reset it when we're done.
850 os.chmod(filename, perms)
851
852 # Use a fixed timestamp so the output is repeatable.
853 epoch = datetime.datetime.fromtimestamp(0)
854 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
855 os.utime(filename, (timestamp, timestamp))
856
857 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
858 finally:
859 os.chmod(filename, saved_stat.st_mode)
860 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
861 zipfile.ZIP64_LIMIT = saved_zip64_limit
862
863
Tao Bao97734652015-05-20 09:32:18 -0700864def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Bao2ed665a2015-04-01 11:21:55 -0700865 compress_type=None):
866 """Wrap zipfile.writestr() function to work around the zip64 limit.
867
868 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
869 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
870 when calling crc32(bytes).
871
872 But it still works fine to write a shorter string into a large zip file.
873 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
874 when we know the string won't be too long.
875 """
876
877 saved_zip64_limit = zipfile.ZIP64_LIMIT
878 zipfile.ZIP64_LIMIT = (1 << 32) - 1
879
880 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
881 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700882 zinfo.compress_type = zip_file.compression
Tao Bao97734652015-05-20 09:32:18 -0700883 if perms is None:
884 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800885 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700886 zinfo = zinfo_or_arcname
887
888 # If compress_type is given, it overrides the value in zinfo.
889 if compress_type is not None:
890 zinfo.compress_type = compress_type
891
Tao Bao97734652015-05-20 09:32:18 -0700892 # If perms is given, it has a priority.
893 if perms is not None:
894 zinfo.external_attr = perms << 16
895
Tao Bao2ed665a2015-04-01 11:21:55 -0700896 # Use a fixed timestamp so the output is repeatable.
Tao Bao2ed665a2015-04-01 11:21:55 -0700897 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
898
Dan Albert8b72aef2015-03-23 19:13:21 -0700899 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700900 zipfile.ZIP64_LIMIT = saved_zip64_limit
901
902
903def ZipClose(zip_file):
904 # http://b/18015246
905 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
906 # central directory.
907 saved_zip64_limit = zipfile.ZIP64_LIMIT
908 zipfile.ZIP64_LIMIT = (1 << 32) - 1
909
910 zip_file.close()
911
912 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700913
914
915class DeviceSpecificParams(object):
916 module = None
917 def __init__(self, **kwargs):
918 """Keyword arguments to the constructor become attributes of this
919 object, which is passed to all functions in the device-specific
920 module."""
921 for k, v in kwargs.iteritems():
922 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800923 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700924
925 if self.module is None:
926 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700927 if not path:
928 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700929 try:
930 if os.path.isdir(path):
931 info = imp.find_module("releasetools", [path])
932 else:
933 d, f = os.path.split(path)
934 b, x = os.path.splitext(f)
935 if x == ".py":
936 f = b
937 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800938 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700939 self.module = imp.load_module("device_specific", *info)
940 except ImportError:
941 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700942
943 def _DoCall(self, function_name, *args, **kwargs):
944 """Call the named function in the device-specific module, passing
945 the given args and kwargs. The first argument to the call will be
946 the DeviceSpecific object itself. If there is no module, or the
947 module does not define the function, return the value of the
948 'default' kwarg (which itself defaults to None)."""
949 if self.module is None or not hasattr(self.module, function_name):
950 return kwargs.get("default", None)
951 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
952
953 def FullOTA_Assertions(self):
954 """Called after emitting the block of assertions at the top of a
955 full OTA package. Implementations can add whatever additional
956 assertions they like."""
957 return self._DoCall("FullOTA_Assertions")
958
Doug Zongkere5ff5902012-01-17 10:55:37 -0800959 def FullOTA_InstallBegin(self):
960 """Called at the start of full OTA installation."""
961 return self._DoCall("FullOTA_InstallBegin")
962
Doug Zongker05d3dea2009-06-22 11:32:31 -0700963 def FullOTA_InstallEnd(self):
964 """Called at the end of full OTA installation; typically this is
965 used to install the image for the device's baseband processor."""
966 return self._DoCall("FullOTA_InstallEnd")
967
968 def IncrementalOTA_Assertions(self):
969 """Called after emitting the block of assertions at the top of an
970 incremental OTA package. Implementations can add whatever
971 additional assertions they like."""
972 return self._DoCall("IncrementalOTA_Assertions")
973
Doug Zongkere5ff5902012-01-17 10:55:37 -0800974 def IncrementalOTA_VerifyBegin(self):
975 """Called at the start of the verification phase of incremental
976 OTA installation; additional checks can be placed here to abort
977 the script before any changes are made."""
978 return self._DoCall("IncrementalOTA_VerifyBegin")
979
Doug Zongker05d3dea2009-06-22 11:32:31 -0700980 def IncrementalOTA_VerifyEnd(self):
981 """Called at the end of the verification phase of incremental OTA
982 installation; additional checks can be placed here to abort the
983 script before any changes are made."""
984 return self._DoCall("IncrementalOTA_VerifyEnd")
985
Doug Zongkere5ff5902012-01-17 10:55:37 -0800986 def IncrementalOTA_InstallBegin(self):
987 """Called at the start of incremental OTA installation (after
988 verification is complete)."""
989 return self._DoCall("IncrementalOTA_InstallBegin")
990
Doug Zongker05d3dea2009-06-22 11:32:31 -0700991 def IncrementalOTA_InstallEnd(self):
992 """Called at the end of incremental OTA installation; typically
993 this is used to install the image for the device's baseband
994 processor."""
995 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700996
997class File(object):
998 def __init__(self, name, data):
999 self.name = name
1000 self.data = data
1001 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001002 self.sha1 = sha1(data).hexdigest()
1003
1004 @classmethod
1005 def FromLocalFile(cls, name, diskname):
1006 f = open(diskname, "rb")
1007 data = f.read()
1008 f.close()
1009 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001010
1011 def WriteToTemp(self):
1012 t = tempfile.NamedTemporaryFile()
1013 t.write(self.data)
1014 t.flush()
1015 return t
1016
Geremy Condra36bd3652014-02-06 19:45:10 -08001017 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001018 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001019
1020DIFF_PROGRAM_BY_EXT = {
1021 ".gz" : "imgdiff",
1022 ".zip" : ["imgdiff", "-z"],
1023 ".jar" : ["imgdiff", "-z"],
1024 ".apk" : ["imgdiff", "-z"],
1025 ".img" : "imgdiff",
1026 }
1027
1028class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001029 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001030 self.tf = tf
1031 self.sf = sf
1032 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001033 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001034
1035 def ComputePatch(self):
1036 """Compute the patch (as a string of data) needed to turn sf into
1037 tf. Returns the same tuple as GetPatch()."""
1038
1039 tf = self.tf
1040 sf = self.sf
1041
Doug Zongker24cd2802012-08-14 16:36:15 -07001042 if self.diff_program:
1043 diff_program = self.diff_program
1044 else:
1045 ext = os.path.splitext(tf.name)[1]
1046 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001047
1048 ttemp = tf.WriteToTemp()
1049 stemp = sf.WriteToTemp()
1050
1051 ext = os.path.splitext(tf.name)[1]
1052
1053 try:
1054 ptemp = tempfile.NamedTemporaryFile()
1055 if isinstance(diff_program, list):
1056 cmd = copy.copy(diff_program)
1057 else:
1058 cmd = [diff_program]
1059 cmd.append(stemp.name)
1060 cmd.append(ttemp.name)
1061 cmd.append(ptemp.name)
1062 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001063 err = []
1064 def run():
1065 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001066 if e:
1067 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001068 th = threading.Thread(target=run)
1069 th.start()
1070 th.join(timeout=300) # 5 mins
1071 if th.is_alive():
1072 print "WARNING: diff command timed out"
1073 p.terminate()
1074 th.join(5)
1075 if th.is_alive():
1076 p.kill()
1077 th.join()
1078
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001079 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001080 print "WARNING: failure running %s:\n%s\n" % (
1081 diff_program, "".join(err))
1082 self.patch = None
1083 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001084 diff = ptemp.read()
1085 finally:
1086 ptemp.close()
1087 stemp.close()
1088 ttemp.close()
1089
1090 self.patch = diff
1091 return self.tf, self.sf, self.patch
1092
1093
1094 def GetPatch(self):
1095 """Return a tuple (target_file, source_file, patch_data).
1096 patch_data may be None if ComputePatch hasn't been called, or if
1097 computing the patch failed."""
1098 return self.tf, self.sf, self.patch
1099
1100
1101def ComputeDifferences(diffs):
1102 """Call ComputePatch on all the Difference objects in 'diffs'."""
1103 print len(diffs), "diffs to compute"
1104
1105 # Do the largest files first, to try and reduce the long-pole effect.
1106 by_size = [(i.tf.size, i) for i in diffs]
1107 by_size.sort(reverse=True)
1108 by_size = [i[1] for i in by_size]
1109
1110 lock = threading.Lock()
1111 diff_iter = iter(by_size) # accessed under lock
1112
1113 def worker():
1114 try:
1115 lock.acquire()
1116 for d in diff_iter:
1117 lock.release()
1118 start = time.time()
1119 d.ComputePatch()
1120 dur = time.time() - start
1121 lock.acquire()
1122
1123 tf, sf, patch = d.GetPatch()
1124 if sf.name == tf.name:
1125 name = tf.name
1126 else:
1127 name = "%s (%s)" % (tf.name, sf.name)
1128 if patch is None:
1129 print "patching failed! %s" % (name,)
1130 else:
1131 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1132 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1133 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001134 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001135 print e
1136 raise
1137
1138 # start worker threads; wait for them all to finish.
1139 threads = [threading.Thread(target=worker)
1140 for i in range(OPTIONS.worker_threads)]
1141 for th in threads:
1142 th.start()
1143 while threads:
1144 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001145
1146
Dan Albert8b72aef2015-03-23 19:13:21 -07001147class BlockDifference(object):
1148 def __init__(self, partition, tgt, src=None, check_first_block=False,
1149 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001150 self.tgt = tgt
1151 self.src = src
1152 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001153 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001154
Tao Baoff777812015-05-12 11:42:31 -07001155 # Due to http://b/20939131, check_first_block is disabled temporarily.
1156 assert not self.check_first_block
1157
Tao Baodd2a5892015-03-12 12:32:37 -07001158 if version is None:
1159 version = 1
1160 if OPTIONS.info_dict:
1161 version = max(
1162 int(i) for i in
1163 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1164 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001165
1166 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001167 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001168 tmpdir = tempfile.mkdtemp()
1169 OPTIONS.tempfiles.append(tmpdir)
1170 self.path = os.path.join(tmpdir, partition)
1171 b.Compute(self.path)
1172
1173 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1174
1175 def WriteScript(self, script, output_zip, progress=None):
1176 if not self.src:
1177 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001178 script.Print("Patching %s image unconditionally..." % (self.partition,))
1179 else:
1180 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001181
Dan Albert8b72aef2015-03-23 19:13:21 -07001182 if progress:
1183 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001184 self._WriteUpdate(script, output_zip)
1185
1186 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001187 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001188 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001189 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001190 else:
Tao Baoff777812015-05-12 11:42:31 -07001191 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1192 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001193 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001194 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1195 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001196 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001197 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baoff777812015-05-12 11:42:31 -07001198 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001199 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001200 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001201 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Baoff777812015-05-12 11:42:31 -07001202 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001203 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001204 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001205
Tao Baodd2a5892015-03-12 12:32:37 -07001206 # When generating incrementals for the system and vendor partitions,
1207 # explicitly check the first block (which contains the superblock) of
1208 # the partition to see if it's what we expect. If this check fails,
1209 # give an explicit log message about the partition having been
1210 # remounted R/W (the most likely explanation) and the need to flash to
1211 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001212 if self.check_first_block:
1213 self._CheckFirstBlock(script)
1214
Tao Baodd2a5892015-03-12 12:32:37 -07001215 # Abort the OTA update. Note that the incremental OTA cannot be applied
1216 # even if it may match the checksum of the target partition.
1217 # a) If version < 3, operations like move and erase will make changes
1218 # unconditionally and damage the partition.
1219 # b) If version >= 3, it won't even reach here.
1220 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1221 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001222
1223 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001224 ZipWrite(output_zip,
1225 '{}.transfer.list'.format(self.path),
1226 '{}.transfer.list'.format(self.partition))
1227 ZipWrite(output_zip,
1228 '{}.new.dat'.format(self.path),
1229 '{}.new.dat'.format(self.partition))
1230 ZipWrite(output_zip,
1231 '{}.patch.dat'.format(self.path),
1232 '{}.patch.dat'.format(self.partition),
1233 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001234
Dan Albert8e0178d2015-01-27 15:53:15 -08001235 call = ('block_image_update("{device}", '
1236 'package_extract_file("{partition}.transfer.list"), '
1237 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1238 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001239 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001240
Dan Albert8b72aef2015-03-23 19:13:21 -07001241 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001242 data = source.ReadRangeSet(ranges)
1243 ctx = sha1()
1244
1245 for p in data:
1246 ctx.update(p)
1247
1248 return ctx.hexdigest()
1249
Tao Baoff777812015-05-12 11:42:31 -07001250 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1251 # remounting R/W. Will change the checking to a finer-grained way to
1252 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001253 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001254 r = rangelib.RangeSet((0, 1))
1255 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001256
1257 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1258 'abort("%s has been remounted R/W; '
1259 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001260 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001261 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001262
1263DataImage = blockimgdiff.DataImage
1264
1265
Doug Zongker96a57e72010-09-26 14:57:41 -07001266# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001267PARTITION_TYPES = {
1268 "yaffs2": "MTD",
1269 "mtd": "MTD",
1270 "ext4": "EMMC",
1271 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001272 "f2fs": "EMMC",
1273 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001274}
Doug Zongker96a57e72010-09-26 14:57:41 -07001275
1276def GetTypeAndDevice(mount_point, info):
1277 fstab = info["fstab"]
1278 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001279 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1280 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001281 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001282 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001283
1284
1285def ParseCertificate(data):
1286 """Parse a PEM-format certificate."""
1287 cert = []
1288 save = False
1289 for line in data.split("\n"):
1290 if "--END CERTIFICATE--" in line:
1291 break
1292 if save:
1293 cert.append(line)
1294 if "--BEGIN CERTIFICATE--" in line:
1295 save = True
1296 cert = "".join(cert).decode('base64')
1297 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001298
Doug Zongker412c02f2014-02-13 10:58:24 -08001299def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1300 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001301 """Generate a binary patch that creates the recovery image starting
1302 with the boot image. (Most of the space in these images is just the
1303 kernel, which is identical for the two, so the resulting patch
1304 should be efficient.) Add it to the output zip, along with a shell
1305 script that is run from init.rc on first boot to actually do the
1306 patching and install the new recovery image.
1307
1308 recovery_img and boot_img should be File objects for the
1309 corresponding images. info should be the dictionary returned by
1310 common.LoadInfoDict() on the input target_files.
1311 """
1312
Doug Zongker412c02f2014-02-13 10:58:24 -08001313 if info_dict is None:
1314 info_dict = OPTIONS.info_dict
1315
Doug Zongkerc9253822014-02-04 12:17:58 -08001316 diff_program = ["imgdiff"]
1317 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1318 if os.path.exists(path):
1319 diff_program.append("-b")
1320 diff_program.append(path)
1321 bonus_args = "-b /system/etc/recovery-resource.dat"
1322 else:
1323 bonus_args = ""
1324
1325 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1326 _, _, patch = d.ComputePatch()
1327 output_sink("recovery-from-boot.p", patch)
1328
Dan Albertebb19aa2015-03-27 19:11:53 -07001329 try:
1330 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1331 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1332 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001333 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001334
1335 sh = """#!/system/bin/sh
1336if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1337 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"
1338else
1339 log -t recovery "Recovery image already installed"
1340fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001341""" % {'boot_size': boot_img.size,
1342 'boot_sha1': boot_img.sha1,
1343 'recovery_size': recovery_img.size,
1344 'recovery_sha1': recovery_img.sha1,
1345 'boot_type': boot_type,
1346 'boot_device': boot_device,
1347 'recovery_type': recovery_type,
1348 'recovery_device': recovery_device,
1349 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001350
1351 # The install script location moved from /system/etc to /system/bin
1352 # in the L release. Parse the init.rc file to find out where the
1353 # target-files expects it to be, and put it there.
1354 sh_location = "etc/install-recovery.sh"
1355 try:
1356 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1357 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001358 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001359 if m:
1360 sh_location = m.group(1)
1361 print "putting script in", sh_location
1362 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001363 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001364 print "failed to read init.rc: %s" % (e,)
1365
1366 output_sink(sh_location, sh)