blob: 7cd70f43a980b7d491693900764313252385d174 [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.
Baligh Uddin1131d892014-09-05 11:18:07 -070047OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070048OPTIONS.public_key_suffix = ".x509.pem"
49OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070050OPTIONS.verbose = False
51OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070052OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080053OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070054OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070055
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080056
57# Values for "certificate" in apkcerts that mean special things.
58SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
59
60
Doug Zongkereef39442009-04-02 12:14:19 -070061class ExternalError(RuntimeError): pass
62
63
64def Run(args, **kwargs):
65 """Create and return a subprocess.Popen object, printing the command
66 line on the terminal if -v was specified."""
67 if OPTIONS.verbose:
68 print " running: ", " ".join(args)
69 return subprocess.Popen(args, **kwargs)
70
71
Ying Wang7e6d4e42010-12-13 16:25:36 -080072def CloseInheritedPipes():
73 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
74 before doing other work."""
75 if platform.system() != "Darwin":
76 return
77 for d in range(3, 1025):
78 try:
79 stat = os.fstat(d)
80 if stat is not None:
81 pipebit = stat[0] & 0x1000
82 if pipebit != 0:
83 os.close(d)
84 except OSError:
85 pass
86
87
Doug Zongker37974732010-09-16 17:44:38 -070088def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070089 """Read and parse the META/misc_info.txt key/value pairs from the
90 input target files and return a dict."""
91
92 d = {}
93 try:
Doug Zongker37974732010-09-16 17:44:38 -070094 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070095 line = line.strip()
96 if not line or line.startswith("#"): continue
97 k, v = line.split("=", 1)
98 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070099 except KeyError:
100 # ok if misc_info.txt doesn't exist
101 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700102
Doug Zongker37974732010-09-16 17:44:38 -0700103 # backwards compatibility: These values used to be in their own
104 # files. Look for them, in case we're processing an old
105 # target_files zip.
106
107 if "mkyaffs2_extra_flags" not in d:
108 try:
109 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
110 except KeyError:
111 # ok if flags don't exist
112 pass
113
114 if "recovery_api_version" not in d:
115 try:
116 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
117 except KeyError:
118 raise ValueError("can't find recovery API version in input target-files")
119
120 if "tool_extensions" not in d:
121 try:
122 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
123 except KeyError:
124 # ok if extensions don't exist
125 pass
126
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800127 if "fstab_version" not in d:
128 d["fstab_version"] = "1"
129
Doug Zongker37974732010-09-16 17:44:38 -0700130 try:
131 data = zip.read("META/imagesizes.txt")
132 for line in data.split("\n"):
133 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700134 name, value = line.split(" ", 1)
135 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700136 if name == "blocksize":
137 d[name] = value
138 else:
139 d[name + "_size"] = value
140 except KeyError:
141 pass
142
143 def makeint(key):
144 if key in d:
145 d[key] = int(d[key], 0)
146
147 makeint("recovery_api_version")
148 makeint("blocksize")
149 makeint("system_size")
150 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700151 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700152 makeint("recovery_size")
153 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800154 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700155
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800156 d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"])
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700157 d["build.prop"] = LoadBuildProp(zip)
158 return d
159
160def LoadBuildProp(zip):
161 try:
162 data = zip.read("SYSTEM/build.prop")
163 except KeyError:
164 print "Warning: could not find SYSTEM/build.prop in %s" % zip
165 data = ""
166
167 d = {}
168 for line in data.split("\n"):
169 line = line.strip()
170 if not line or line.startswith("#"): continue
171 name, value = line.split("=", 1)
172 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700173 return d
174
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175def LoadRecoveryFSTab(zip, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700176 class Partition(object):
177 pass
178
179 try:
180 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
181 except KeyError:
Jeff Davidson033fbe22011-10-26 18:08:09 -0700182 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip
183 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700184
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800185 if fstab_version == 1:
186 d = {}
187 for line in data.split("\n"):
188 line = line.strip()
189 if not line or line.startswith("#"): continue
190 pieces = line.split()
191 if not (3 <= len(pieces) <= 4):
192 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700193
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800194 p = Partition()
195 p.mount_point = pieces[0]
196 p.fs_type = pieces[1]
197 p.device = pieces[2]
198 p.length = 0
199 options = None
200 if len(pieces) >= 4:
201 if pieces[3].startswith("/"):
202 p.device2 = pieces[3]
203 if len(pieces) >= 5:
204 options = pieces[4]
205 else:
206 p.device2 = None
207 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800208 else:
209 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700210
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800211 if options:
212 options = options.split(",")
213 for i in options:
214 if i.startswith("length="):
215 p.length = int(i[7:])
216 else:
217 print "%s: unknown option \"%s\"" % (p.mount_point, i)
218
219 d[p.mount_point] = p
220
221 elif fstab_version == 2:
222 d = {}
223 for line in data.split("\n"):
224 line = line.strip()
225 if not line or line.startswith("#"): continue
226 pieces = line.split()
227 if len(pieces) != 5:
228 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
229
230 # Ignore entries that are managed by vold
231 options = pieces[4]
232 if "voldmanaged=" in options: continue
233
234 # It's a good line, parse it
235 p = Partition()
236 p.device = pieces[0]
237 p.mount_point = pieces[1]
238 p.fs_type = pieces[2]
239 p.device2 = None
240 p.length = 0
241
Doug Zongker086cbb02011-02-17 15:54:20 -0800242 options = options.split(",")
243 for i in options:
244 if i.startswith("length="):
245 p.length = int(i[7:])
246 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 # Ignore all unknown options in the unified fstab
248 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800249
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800250 d[p.mount_point] = p
251
252 else:
253 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
254
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700255 return d
256
257
Doug Zongker37974732010-09-16 17:44:38 -0700258def DumpInfoDict(d):
259 for k, v in sorted(d.items()):
260 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700261
Doug Zongkerd5131602012-08-02 14:46:42 -0700262def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700263 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700264 'sourcedir'), and turn them into a boot image. Return the image
265 data, or None if sourcedir does not appear to contains files for
266 building the requested image."""
267
268 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
269 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
270 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700271
Doug Zongkerd5131602012-08-02 14:46:42 -0700272 if info_dict is None:
273 info_dict = OPTIONS.info_dict
274
Doug Zongkereef39442009-04-02 12:14:19 -0700275 ramdisk_img = tempfile.NamedTemporaryFile()
276 img = tempfile.NamedTemporaryFile()
277
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700278 if os.access(fs_config_file, os.F_OK):
279 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
280 else:
281 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
282 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700283 p2 = Run(["minigzip"],
284 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700285
286 p2.wait()
287 p1.wait()
288 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700289 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700290
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800291 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
292 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
293
294 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700295
Doug Zongker171f1cd2009-06-15 22:36:37 -0700296 fn = os.path.join(sourcedir, "cmdline")
297 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700298 cmd.append("--cmdline")
299 cmd.append(open(fn).read().rstrip("\n"))
300
301 fn = os.path.join(sourcedir, "base")
302 if os.access(fn, os.F_OK):
303 cmd.append("--base")
304 cmd.append(open(fn).read().rstrip("\n"))
305
Ying Wang4de6b5b2010-08-25 14:29:34 -0700306 fn = os.path.join(sourcedir, "pagesize")
307 if os.access(fn, os.F_OK):
308 cmd.append("--pagesize")
309 cmd.append(open(fn).read().rstrip("\n"))
310
Doug Zongkerd5131602012-08-02 14:46:42 -0700311 args = info_dict.get("mkbootimg_args", None)
312 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700313 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700314
Doug Zongker38a649f2009-06-17 09:07:09 -0700315 cmd.extend(["--ramdisk", ramdisk_img.name,
316 "--output", img.name])
317
318 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700319 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700320 assert p.returncode == 0, "mkbootimg of %s image failed" % (
321 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700322
323 img.seek(os.SEEK_SET, 0)
324 data = img.read()
325
326 ramdisk_img.close()
327 img.close()
328
329 return data
330
331
Doug Zongkerd5131602012-08-02 14:46:42 -0700332def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
333 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800334 """Return a File object (with name 'name') with the desired bootable
335 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
336 'prebuilt_name', otherwise construct it from the source files in
337 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700338
Doug Zongker55d93282011-01-25 17:03:34 -0800339 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
340 if os.path.exists(prebuilt_path):
341 print "using prebuilt %s..." % (prebuilt_name,)
342 return File.FromLocalFile(name, prebuilt_path)
343 else:
344 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700345 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Ying Wangeafdd2c2014-02-05 11:28:51 -0800346 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
347 os.path.join(unpack_dir, fs_config),
348 info_dict)
349 if data:
350 return File(name, data)
351 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800352
Doug Zongkereef39442009-04-02 12:14:19 -0700353
Doug Zongker75f17362009-12-08 13:46:44 -0800354def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800355 """Unzip the given archive into a temporary directory and return the name.
356
357 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
358 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
359
360 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
361 main file), open for reading.
362 """
Doug Zongkereef39442009-04-02 12:14:19 -0700363
364 tmp = tempfile.mkdtemp(prefix="targetfiles-")
365 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800366
367 def unzip_to_dir(filename, dirname):
368 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
369 if pattern is not None:
370 cmd.append(pattern)
371 p = Run(cmd, stdout=subprocess.PIPE)
372 p.communicate()
373 if p.returncode != 0:
374 raise ExternalError("failed to unzip input target-files \"%s\"" %
375 (filename,))
376
377 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
378 if m:
379 unzip_to_dir(m.group(1), tmp)
380 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
381 filename = m.group(1)
382 else:
383 unzip_to_dir(filename, tmp)
384
385 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700386
387
388def GetKeyPasswords(keylist):
389 """Given a list of keys, prompt the user to enter passwords for
390 those which require them. Return a {key: password} dict. password
391 will be None if the key has no password."""
392
Doug Zongker8ce7c252009-05-22 13:34:54 -0700393 no_passwords = []
394 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700395 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700396 devnull = open("/dev/null", "w+b")
397 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800398 # We don't need a password for things that aren't really keys.
399 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700400 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700401 continue
402
T.R. Fullhart37e10522013-03-18 10:31:26 -0700403 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700404 "-inform", "DER", "-nocrypt"],
405 stdin=devnull.fileno(),
406 stdout=devnull.fileno(),
407 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700408 p.communicate()
409 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700410 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700411 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700412 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700413 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
414 "-inform", "DER", "-passin", "pass:"],
415 stdin=devnull.fileno(),
416 stdout=devnull.fileno(),
417 stderr=subprocess.PIPE)
418 stdout, stderr = p.communicate()
419 if p.returncode == 0:
420 # Encrypted key with empty string as password.
421 key_passwords[k] = ''
422 elif stderr.startswith('Error decrypting key'):
423 # Definitely encrypted key.
424 # It would have said "Error reading key" if it didn't parse correctly.
425 need_passwords.append(k)
426 else:
427 # Potentially, a type of key that openssl doesn't understand.
428 # We'll let the routines in signapk.jar handle it.
429 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700430 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700431
T.R. Fullhart37e10522013-03-18 10:31:26 -0700432 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700433 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700434 return key_passwords
435
436
Doug Zongker951495f2009-08-14 12:44:19 -0700437def SignFile(input_name, output_name, key, password, align=None,
438 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700439 """Sign the input_name zip/jar/apk, producing output_name. Use the
440 given key and password (the latter may be None if the key does not
441 have a password.
442
443 If align is an integer > 1, zipalign is run to align stored files in
444 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700445
446 If whole_file is true, use the "-w" option to SignApk to embed a
447 signature that covers the whole file in the archive comment of the
448 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700449 """
Doug Zongker951495f2009-08-14 12:44:19 -0700450
Doug Zongkereef39442009-04-02 12:14:19 -0700451 if align == 0 or align == 1:
452 align = None
453
454 if align:
455 temp = tempfile.NamedTemporaryFile()
456 sign_name = temp.name
457 else:
458 sign_name = output_name
459
Baligh Uddin1131d892014-09-05 11:18:07 -0700460 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700461 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
462 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700463 if whole_file:
464 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700465 cmd.extend([key + OPTIONS.public_key_suffix,
466 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700467 input_name, sign_name])
468
469 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700470 if password is not None:
471 password += "\n"
472 p.communicate(password)
473 if p.returncode != 0:
474 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
475
476 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700477 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700478 p.communicate()
479 if p.returncode != 0:
480 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
481 temp.close()
482
483
Doug Zongker37974732010-09-16 17:44:38 -0700484def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700485 """Check the data string passed against the max size limit, if
486 any, for the given target. Raise exception if the data is too big.
487 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700488
Doug Zongker1684d9c2010-09-17 07:44:38 -0700489 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700490 mount_point = "/" + target
491
Ying Wang2a386e02014-06-03 14:07:27 -0700492 fs_type = None
493 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700494 if info_dict["fstab"]:
495 if mount_point == "/userdata": mount_point = "/data"
496 p = info_dict["fstab"][mount_point]
497 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800498 device = p.device
499 if "/" in device:
500 device = device[device.rfind("/")+1:]
501 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700502 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700503
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700504 if fs_type == "yaffs2":
505 # image size should be increased by 1/64th to account for the
506 # spare area (64 bytes per 2k page)
507 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800508 size = len(data)
509 pct = float(size) * 100.0 / limit
510 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
511 if pct >= 99.0:
512 raise ExternalError(msg)
513 elif pct >= 95.0:
514 print
515 print " WARNING: ", msg
516 print
517 elif OPTIONS.verbose:
518 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700519
520
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800521def ReadApkCerts(tf_zip):
522 """Given a target_files ZipFile, parse the META/apkcerts.txt file
523 and return a {package: cert} dict."""
524 certmap = {}
525 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
526 line = line.strip()
527 if not line: continue
528 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
529 r'private_key="(.*)"$', line)
530 if m:
531 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700532 public_key_suffix_len = len(OPTIONS.public_key_suffix)
533 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800534 if cert in SPECIAL_CERT_STRINGS and not privkey:
535 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700536 elif (cert.endswith(OPTIONS.public_key_suffix) and
537 privkey.endswith(OPTIONS.private_key_suffix) and
538 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
539 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800540 else:
541 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
542 return certmap
543
544
Doug Zongkereef39442009-04-02 12:14:19 -0700545COMMON_DOCSTRING = """
546 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700547 Prepend <dir>/bin to the list of places to search for binaries
548 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700549
Doug Zongker05d3dea2009-06-22 11:32:31 -0700550 -s (--device_specific) <file>
551 Path to the python module containing device-specific
552 releasetools code.
553
Doug Zongker8bec09e2009-11-30 15:37:14 -0800554 -x (--extra) <key=value>
555 Add a key/value pair to the 'extras' dict, which device-specific
556 extension code may look at.
557
Doug Zongkereef39442009-04-02 12:14:19 -0700558 -v (--verbose)
559 Show command lines being executed.
560
561 -h (--help)
562 Display this usage message and exit.
563"""
564
565def Usage(docstring):
566 print docstring.rstrip("\n")
567 print COMMON_DOCSTRING
568
569
570def ParseOptions(argv,
571 docstring,
572 extra_opts="", extra_long_opts=(),
573 extra_option_handler=None):
574 """Parse the options in argv and return any arguments that aren't
575 flags. docstring is the calling module's docstring, to be displayed
576 for errors and -h. extra_opts and extra_long_opts are for flags
577 defined by the caller, which are processed by passing them to
578 extra_option_handler."""
579
580 try:
581 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800582 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700583 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinf5085a82014-09-05 17:36:20 -0700584 "java_path=", "java_args=", "public_key_suffix=",
585 "private_key_suffix=", "device_specific=", "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700586 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700587 except getopt.GetoptError, err:
588 Usage(docstring)
589 print "**", str(err), "**"
590 sys.exit(2)
591
592 path_specified = False
593
594 for o, a in opts:
595 if o in ("-h", "--help"):
596 Usage(docstring)
597 sys.exit()
598 elif o in ("-v", "--verbose"):
599 OPTIONS.verbose = True
600 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700601 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700602 elif o in ("--signapk_path",):
603 OPTIONS.signapk_path = a
604 elif o in ("--extra_signapk_args",):
605 OPTIONS.extra_signapk_args = shlex.split(a)
606 elif o in ("--java_path",):
607 OPTIONS.java_path = a
Baligh Uddin1131d892014-09-05 11:18:07 -0700608 elif o in ("--java_args",):
609 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700610 elif o in ("--public_key_suffix",):
611 OPTIONS.public_key_suffix = a
612 elif o in ("--private_key_suffix",):
613 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700614 elif o in ("-s", "--device_specific"):
615 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800616 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800617 key, value = a.split("=", 1)
618 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700619 else:
620 if extra_option_handler is None or not extra_option_handler(o, a):
621 assert False, "unknown option \"%s\"" % (o,)
622
Doug Zongker602a84e2009-06-18 08:35:12 -0700623 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
624 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700625
626 return args
627
628
629def Cleanup():
630 for i in OPTIONS.tempfiles:
631 if os.path.isdir(i):
632 shutil.rmtree(i)
633 else:
634 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700635
636
637class PasswordManager(object):
638 def __init__(self):
639 self.editor = os.getenv("EDITOR", None)
640 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
641
642 def GetPasswords(self, items):
643 """Get passwords corresponding to each string in 'items',
644 returning a dict. (The dict may have keys in addition to the
645 values in 'items'.)
646
647 Uses the passwords in $ANDROID_PW_FILE if available, letting the
648 user edit that file to add more needed passwords. If no editor is
649 available, or $ANDROID_PW_FILE isn't define, prompts the user
650 interactively in the ordinary way.
651 """
652
653 current = self.ReadFile()
654
655 first = True
656 while True:
657 missing = []
658 for i in items:
659 if i not in current or not current[i]:
660 missing.append(i)
661 # Are all the passwords already in the file?
662 if not missing: return current
663
664 for i in missing:
665 current[i] = ""
666
667 if not first:
668 print "key file %s still missing some passwords." % (self.pwfile,)
669 answer = raw_input("try to edit again? [y]> ").strip()
670 if answer and answer[0] not in 'yY':
671 raise RuntimeError("key passwords unavailable")
672 first = False
673
674 current = self.UpdateAndReadFile(current)
675
676 def PromptResult(self, current):
677 """Prompt the user to enter a value (password) for each key in
678 'current' whose value is fales. Returns a new dict with all the
679 values.
680 """
681 result = {}
682 for k, v in sorted(current.iteritems()):
683 if v:
684 result[k] = v
685 else:
686 while True:
687 result[k] = getpass.getpass("Enter password for %s key> "
688 % (k,)).strip()
689 if result[k]: break
690 return result
691
692 def UpdateAndReadFile(self, current):
693 if not self.editor or not self.pwfile:
694 return self.PromptResult(current)
695
696 f = open(self.pwfile, "w")
697 os.chmod(self.pwfile, 0600)
698 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
699 f.write("# (Additional spaces are harmless.)\n\n")
700
701 first_line = None
702 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
703 sorted.sort()
704 for i, (_, k, v) in enumerate(sorted):
705 f.write("[[[ %s ]]] %s\n" % (v, k))
706 if not v and first_line is None:
707 # position cursor on first line with no password.
708 first_line = i + 4
709 f.close()
710
711 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
712 _, _ = p.communicate()
713
714 return self.ReadFile()
715
716 def ReadFile(self):
717 result = {}
718 if self.pwfile is None: return result
719 try:
720 f = open(self.pwfile, "r")
721 for line in f:
722 line = line.strip()
723 if not line or line[0] == '#': continue
724 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
725 if not m:
726 print "failed to parse password file: ", line
727 else:
728 result[m.group(2)] = m.group(1)
729 f.close()
730 except IOError, e:
731 if e.errno != errno.ENOENT:
732 print "error reading password file: ", str(e)
733 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700734
735
736def ZipWriteStr(zip, filename, data, perms=0644):
737 # use a fixed timestamp so the output is repeatable.
738 zinfo = zipfile.ZipInfo(filename=filename,
739 date_time=(2009, 1, 1, 0, 0, 0))
740 zinfo.compress_type = zip.compression
741 zinfo.external_attr = perms << 16
742 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700743
744
745class DeviceSpecificParams(object):
746 module = None
747 def __init__(self, **kwargs):
748 """Keyword arguments to the constructor become attributes of this
749 object, which is passed to all functions in the device-specific
750 module."""
751 for k, v in kwargs.iteritems():
752 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800753 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700754
755 if self.module is None:
756 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700757 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700758 try:
759 if os.path.isdir(path):
760 info = imp.find_module("releasetools", [path])
761 else:
762 d, f = os.path.split(path)
763 b, x = os.path.splitext(f)
764 if x == ".py":
765 f = b
766 info = imp.find_module(f, [d])
767 self.module = imp.load_module("device_specific", *info)
768 except ImportError:
769 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700770
771 def _DoCall(self, function_name, *args, **kwargs):
772 """Call the named function in the device-specific module, passing
773 the given args and kwargs. The first argument to the call will be
774 the DeviceSpecific object itself. If there is no module, or the
775 module does not define the function, return the value of the
776 'default' kwarg (which itself defaults to None)."""
777 if self.module is None or not hasattr(self.module, function_name):
778 return kwargs.get("default", None)
779 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
780
781 def FullOTA_Assertions(self):
782 """Called after emitting the block of assertions at the top of a
783 full OTA package. Implementations can add whatever additional
784 assertions they like."""
785 return self._DoCall("FullOTA_Assertions")
786
Doug Zongkere5ff5902012-01-17 10:55:37 -0800787 def FullOTA_InstallBegin(self):
788 """Called at the start of full OTA installation."""
789 return self._DoCall("FullOTA_InstallBegin")
790
Doug Zongker05d3dea2009-06-22 11:32:31 -0700791 def FullOTA_InstallEnd(self):
792 """Called at the end of full OTA installation; typically this is
793 used to install the image for the device's baseband processor."""
794 return self._DoCall("FullOTA_InstallEnd")
795
796 def IncrementalOTA_Assertions(self):
797 """Called after emitting the block of assertions at the top of an
798 incremental OTA package. Implementations can add whatever
799 additional assertions they like."""
800 return self._DoCall("IncrementalOTA_Assertions")
801
Doug Zongkere5ff5902012-01-17 10:55:37 -0800802 def IncrementalOTA_VerifyBegin(self):
803 """Called at the start of the verification phase of incremental
804 OTA installation; additional checks can be placed here to abort
805 the script before any changes are made."""
806 return self._DoCall("IncrementalOTA_VerifyBegin")
807
Doug Zongker05d3dea2009-06-22 11:32:31 -0700808 def IncrementalOTA_VerifyEnd(self):
809 """Called at the end of the verification phase of incremental OTA
810 installation; additional checks can be placed here to abort the
811 script before any changes are made."""
812 return self._DoCall("IncrementalOTA_VerifyEnd")
813
Doug Zongkere5ff5902012-01-17 10:55:37 -0800814 def IncrementalOTA_InstallBegin(self):
815 """Called at the start of incremental OTA installation (after
816 verification is complete)."""
817 return self._DoCall("IncrementalOTA_InstallBegin")
818
Doug Zongker05d3dea2009-06-22 11:32:31 -0700819 def IncrementalOTA_InstallEnd(self):
820 """Called at the end of incremental OTA installation; typically
821 this is used to install the image for the device's baseband
822 processor."""
823 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700824
825class File(object):
826 def __init__(self, name, data):
827 self.name = name
828 self.data = data
829 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800830 self.sha1 = sha1(data).hexdigest()
831
832 @classmethod
833 def FromLocalFile(cls, name, diskname):
834 f = open(diskname, "rb")
835 data = f.read()
836 f.close()
837 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700838
839 def WriteToTemp(self):
840 t = tempfile.NamedTemporaryFile()
841 t.write(self.data)
842 t.flush()
843 return t
844
845 def AddToZip(self, z):
846 ZipWriteStr(z, self.name, self.data)
847
848DIFF_PROGRAM_BY_EXT = {
849 ".gz" : "imgdiff",
850 ".zip" : ["imgdiff", "-z"],
851 ".jar" : ["imgdiff", "-z"],
852 ".apk" : ["imgdiff", "-z"],
853 ".img" : "imgdiff",
854 }
855
856class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700857 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700858 self.tf = tf
859 self.sf = sf
860 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700861 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700862
863 def ComputePatch(self):
864 """Compute the patch (as a string of data) needed to turn sf into
865 tf. Returns the same tuple as GetPatch()."""
866
867 tf = self.tf
868 sf = self.sf
869
Doug Zongker24cd2802012-08-14 16:36:15 -0700870 if self.diff_program:
871 diff_program = self.diff_program
872 else:
873 ext = os.path.splitext(tf.name)[1]
874 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700875
876 ttemp = tf.WriteToTemp()
877 stemp = sf.WriteToTemp()
878
879 ext = os.path.splitext(tf.name)[1]
880
881 try:
882 ptemp = tempfile.NamedTemporaryFile()
883 if isinstance(diff_program, list):
884 cmd = copy.copy(diff_program)
885 else:
886 cmd = [diff_program]
887 cmd.append(stemp.name)
888 cmd.append(ttemp.name)
889 cmd.append(ptemp.name)
890 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
891 _, err = p.communicate()
892 if err or p.returncode != 0:
893 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
894 return None
895 diff = ptemp.read()
896 finally:
897 ptemp.close()
898 stemp.close()
899 ttemp.close()
900
901 self.patch = diff
902 return self.tf, self.sf, self.patch
903
904
905 def GetPatch(self):
906 """Return a tuple (target_file, source_file, patch_data).
907 patch_data may be None if ComputePatch hasn't been called, or if
908 computing the patch failed."""
909 return self.tf, self.sf, self.patch
910
911
912def ComputeDifferences(diffs):
913 """Call ComputePatch on all the Difference objects in 'diffs'."""
914 print len(diffs), "diffs to compute"
915
916 # Do the largest files first, to try and reduce the long-pole effect.
917 by_size = [(i.tf.size, i) for i in diffs]
918 by_size.sort(reverse=True)
919 by_size = [i[1] for i in by_size]
920
921 lock = threading.Lock()
922 diff_iter = iter(by_size) # accessed under lock
923
924 def worker():
925 try:
926 lock.acquire()
927 for d in diff_iter:
928 lock.release()
929 start = time.time()
930 d.ComputePatch()
931 dur = time.time() - start
932 lock.acquire()
933
934 tf, sf, patch = d.GetPatch()
935 if sf.name == tf.name:
936 name = tf.name
937 else:
938 name = "%s (%s)" % (tf.name, sf.name)
939 if patch is None:
940 print "patching failed! %s" % (name,)
941 else:
942 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
943 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
944 lock.release()
945 except Exception, e:
946 print e
947 raise
948
949 # start worker threads; wait for them all to finish.
950 threads = [threading.Thread(target=worker)
951 for i in range(OPTIONS.worker_threads)]
952 for th in threads:
953 th.start()
954 while threads:
955 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700956
957
958# map recovery.fstab's fs_types to mount/format "partition types"
959PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
960 "ext4": "EMMC", "emmc": "EMMC" }
961
962def GetTypeAndDevice(mount_point, info):
963 fstab = info["fstab"]
964 if fstab:
965 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
966 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800967 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +0000968
969
970def ParseCertificate(data):
971 """Parse a PEM-format certificate."""
972 cert = []
973 save = False
974 for line in data.split("\n"):
975 if "--END CERTIFICATE--" in line:
976 break
977 if save:
978 cert.append(line)
979 if "--BEGIN CERTIFICATE--" in line:
980 save = True
981 cert = "".join(cert).decode('base64')
982 return cert