blob: 80bb10aea6d0343dd3aca7b943773c2bc4ca4a51 [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. Fullharta28acc62013-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. Fullharta28acc62013-03-18 10:31:26 -070044OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
45OPTIONS.extra_signapk_args = []
46OPTIONS.java_path = "java" # Use the one on the path by default.
47OPTIONS.public_key_suffix = ".x509.pem"
48OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070049OPTIONS.verbose = False
50OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070051OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080052OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070053OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070054
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080055
56# Values for "certificate" in apkcerts that mean special things.
57SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
58
59
Doug Zongkereef39442009-04-02 12:14:19 -070060class ExternalError(RuntimeError): pass
61
62
63def Run(args, **kwargs):
64 """Create and return a subprocess.Popen object, printing the command
65 line on the terminal if -v was specified."""
66 if OPTIONS.verbose:
67 print " running: ", " ".join(args)
68 return subprocess.Popen(args, **kwargs)
69
70
Ying Wang7e6d4e42010-12-13 16:25:36 -080071def CloseInheritedPipes():
72 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
73 before doing other work."""
74 if platform.system() != "Darwin":
75 return
76 for d in range(3, 1025):
77 try:
78 stat = os.fstat(d)
79 if stat is not None:
80 pipebit = stat[0] & 0x1000
81 if pipebit != 0:
82 os.close(d)
83 except OSError:
84 pass
85
86
Doug Zongker37974732010-09-16 17:44:38 -070087def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070088 """Read and parse the META/misc_info.txt key/value pairs from the
89 input target files and return a dict."""
90
91 d = {}
92 try:
Doug Zongker37974732010-09-16 17:44:38 -070093 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070094 line = line.strip()
95 if not line or line.startswith("#"): continue
96 k, v = line.split("=", 1)
97 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070098 except KeyError:
99 # ok if misc_info.txt doesn't exist
100 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101
Doug Zongker37974732010-09-16 17:44:38 -0700102 # backwards compatibility: These values used to be in their own
103 # files. Look for them, in case we're processing an old
104 # target_files zip.
105
106 if "mkyaffs2_extra_flags" not in d:
107 try:
108 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
109 except KeyError:
110 # ok if flags don't exist
111 pass
112
113 if "recovery_api_version" not in d:
114 try:
115 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
116 except KeyError:
117 raise ValueError("can't find recovery API version in input target-files")
118
119 if "tool_extensions" not in d:
120 try:
121 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
122 except KeyError:
123 # ok if extensions don't exist
124 pass
125
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800126 if "fstab_version" not in d:
127 d["fstab_version"] = "1"
128
Doug Zongker37974732010-09-16 17:44:38 -0700129 try:
130 data = zip.read("META/imagesizes.txt")
131 for line in data.split("\n"):
132 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700133 name, value = line.split(" ", 1)
134 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700135 if name == "blocksize":
136 d[name] = value
137 else:
138 d[name + "_size"] = value
139 except KeyError:
140 pass
141
142 def makeint(key):
143 if key in d:
144 d[key] = int(d[key], 0)
145
146 makeint("recovery_api_version")
147 makeint("blocksize")
148 makeint("system_size")
149 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700150 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700151 makeint("recovery_size")
152 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800153 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700154
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800155 d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"])
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700156 d["build.prop"] = LoadBuildProp(zip)
157 return d
158
159def LoadBuildProp(zip):
160 try:
161 data = zip.read("SYSTEM/build.prop")
162 except KeyError:
163 print "Warning: could not find SYSTEM/build.prop in %s" % zip
164 data = ""
165
166 d = {}
167 for line in data.split("\n"):
168 line = line.strip()
169 if not line or line.startswith("#"): continue
170 name, value = line.split("=", 1)
171 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172 return d
173
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800174def LoadRecoveryFSTab(zip, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700175 class Partition(object):
176 pass
177
178 try:
179 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
180 except KeyError:
Jeff Davidson033fbe22011-10-26 18:08:09 -0700181 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip
182 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700183
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800184 if fstab_version == 1:
185 d = {}
186 for line in data.split("\n"):
187 line = line.strip()
188 if not line or line.startswith("#"): continue
189 pieces = line.split()
190 if not (3 <= len(pieces) <= 4):
191 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700192
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800193 p = Partition()
194 p.mount_point = pieces[0]
195 p.fs_type = pieces[1]
196 p.device = pieces[2]
197 p.length = 0
198 options = None
199 if len(pieces) >= 4:
200 if pieces[3].startswith("/"):
201 p.device2 = pieces[3]
202 if len(pieces) >= 5:
203 options = pieces[4]
204 else:
205 p.device2 = None
206 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800207 else:
208 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700209
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800210 if options:
211 options = options.split(",")
212 for i in options:
213 if i.startswith("length="):
214 p.length = int(i[7:])
215 else:
216 print "%s: unknown option \"%s\"" % (p.mount_point, i)
217
218 d[p.mount_point] = p
219
220 elif fstab_version == 2:
221 d = {}
222 for line in data.split("\n"):
223 line = line.strip()
224 if not line or line.startswith("#"): continue
225 pieces = line.split()
226 if len(pieces) != 5:
227 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
228
229 # Ignore entries that are managed by vold
230 options = pieces[4]
231 if "voldmanaged=" in options: continue
232
233 # It's a good line, parse it
234 p = Partition()
235 p.device = pieces[0]
236 p.mount_point = pieces[1]
237 p.fs_type = pieces[2]
238 p.device2 = None
239 p.length = 0
240
Doug Zongker086cbb02011-02-17 15:54:20 -0800241 options = options.split(",")
242 for i in options:
243 if i.startswith("length="):
244 p.length = int(i[7:])
245 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246 # Ignore all unknown options in the unified fstab
247 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800248
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249 d[p.mount_point] = p
250
251 else:
252 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
253
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700254 return d
255
256
Doug Zongker37974732010-09-16 17:44:38 -0700257def DumpInfoDict(d):
258 for k, v in sorted(d.items()):
259 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700260
Doug Zongkerd5131602012-08-02 14:46:42 -0700261def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700262 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700263 'sourcedir'), and turn them into a boot image. Return the image
264 data, or None if sourcedir does not appear to contains files for
265 building the requested image."""
266
267 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
268 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
269 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700270
Doug Zongkerd5131602012-08-02 14:46:42 -0700271 if info_dict is None:
272 info_dict = OPTIONS.info_dict
273
Doug Zongkereef39442009-04-02 12:14:19 -0700274 ramdisk_img = tempfile.NamedTemporaryFile()
275 img = tempfile.NamedTemporaryFile()
276
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700277 if os.access(fs_config_file, os.F_OK):
278 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
279 else:
280 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
281 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700282 p2 = Run(["minigzip"],
283 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700284
285 p2.wait()
286 p1.wait()
287 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700288 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700289
Doug Zongker38a649f2009-06-17 09:07:09 -0700290 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
291
Doug Zongker171f1cd2009-06-15 22:36:37 -0700292 fn = os.path.join(sourcedir, "cmdline")
293 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700294 cmd.append("--cmdline")
295 cmd.append(open(fn).read().rstrip("\n"))
296
297 fn = os.path.join(sourcedir, "base")
298 if os.access(fn, os.F_OK):
299 cmd.append("--base")
300 cmd.append(open(fn).read().rstrip("\n"))
301
Ying Wang4de6b5b2010-08-25 14:29:34 -0700302 fn = os.path.join(sourcedir, "pagesize")
303 if os.access(fn, os.F_OK):
304 cmd.append("--pagesize")
305 cmd.append(open(fn).read().rstrip("\n"))
306
Doug Zongkerd5131602012-08-02 14:46:42 -0700307 args = info_dict.get("mkbootimg_args", None)
308 if args and args.strip():
309 cmd.extend(args.split())
310
Doug Zongker38a649f2009-06-17 09:07:09 -0700311 cmd.extend(["--ramdisk", ramdisk_img.name,
312 "--output", img.name])
313
314 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700315 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700316 assert p.returncode == 0, "mkbootimg of %s image failed" % (
317 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700318
319 img.seek(os.SEEK_SET, 0)
320 data = img.read()
321
322 ramdisk_img.close()
323 img.close()
324
325 return data
326
327
Doug Zongkerd5131602012-08-02 14:46:42 -0700328def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
329 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800330 """Return a File object (with name 'name') with the desired bootable
331 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
332 'prebuilt_name', otherwise construct it from the source files in
333 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700334
Doug Zongker55d93282011-01-25 17:03:34 -0800335 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
336 if os.path.exists(prebuilt_path):
337 print "using prebuilt %s..." % (prebuilt_name,)
338 return File.FromLocalFile(name, prebuilt_path)
339 else:
340 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700341 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
342 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
Doug Zongkerd5131602012-08-02 14:46:42 -0700343 os.path.join(unpack_dir, fs_config),
344 info_dict))
Doug Zongker55d93282011-01-25 17:03:34 -0800345
Doug Zongkereef39442009-04-02 12:14:19 -0700346
Doug Zongker75f17362009-12-08 13:46:44 -0800347def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800348 """Unzip the given archive into a temporary directory and return the name.
349
350 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
351 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
352
353 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
354 main file), open for reading.
355 """
Doug Zongkereef39442009-04-02 12:14:19 -0700356
357 tmp = tempfile.mkdtemp(prefix="targetfiles-")
358 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800359
360 def unzip_to_dir(filename, dirname):
361 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
362 if pattern is not None:
363 cmd.append(pattern)
364 p = Run(cmd, stdout=subprocess.PIPE)
365 p.communicate()
366 if p.returncode != 0:
367 raise ExternalError("failed to unzip input target-files \"%s\"" %
368 (filename,))
369
370 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
371 if m:
372 unzip_to_dir(m.group(1), tmp)
373 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
374 filename = m.group(1)
375 else:
376 unzip_to_dir(filename, tmp)
377
378 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700379
380
381def GetKeyPasswords(keylist):
382 """Given a list of keys, prompt the user to enter passwords for
383 those which require them. Return a {key: password} dict. password
384 will be None if the key has no password."""
385
Doug Zongker8ce7c252009-05-22 13:34:54 -0700386 no_passwords = []
387 need_passwords = []
T.R. Fullharta28acc62013-03-18 10:31:26 -0700388 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700389 devnull = open("/dev/null", "w+b")
390 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800391 # We don't need a password for things that aren't really keys.
392 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700393 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700394 continue
395
T.R. Fullharta28acc62013-03-18 10:31:26 -0700396 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700397 "-inform", "DER", "-nocrypt"],
398 stdin=devnull.fileno(),
399 stdout=devnull.fileno(),
400 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700401 p.communicate()
402 if p.returncode == 0:
T.R. Fullharta28acc62013-03-18 10:31:26 -0700403 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700404 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700405 else:
T.R. Fullharta28acc62013-03-18 10:31:26 -0700406 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
407 "-inform", "DER", "-passin", "pass:"],
408 stdin=devnull.fileno(),
409 stdout=devnull.fileno(),
410 stderr=subprocess.PIPE)
411 stdout, stderr = p.communicate()
412 if p.returncode == 0:
413 # Encrypted key with empty string as password.
414 key_passwords[k] = ''
415 elif stderr.startswith('Error decrypting key'):
416 # Definitely encrypted key.
417 # It would have said "Error reading key" if it didn't parse correctly.
418 need_passwords.append(k)
419 else:
420 # Potentially, a type of key that openssl doesn't understand.
421 # We'll let the routines in signapk.jar handle it.
422 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700423 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700424
T.R. Fullharta28acc62013-03-18 10:31:26 -0700425 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700426 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700427 return key_passwords
428
429
Doug Zongker951495f2009-08-14 12:44:19 -0700430def SignFile(input_name, output_name, key, password, align=None,
431 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700432 """Sign the input_name zip/jar/apk, producing output_name. Use the
433 given key and password (the latter may be None if the key does not
434 have a password.
435
436 If align is an integer > 1, zipalign is run to align stored files in
437 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700438
439 If whole_file is true, use the "-w" option to SignApk to embed a
440 signature that covers the whole file in the archive comment of the
441 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700442 """
Doug Zongker951495f2009-08-14 12:44:19 -0700443
Doug Zongkereef39442009-04-02 12:14:19 -0700444 if align == 0 or align == 1:
445 align = None
446
447 if align:
448 temp = tempfile.NamedTemporaryFile()
449 sign_name = temp.name
450 else:
451 sign_name = output_name
452
T.R. Fullharta28acc62013-03-18 10:31:26 -0700453 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
454 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
455 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700456 if whole_file:
457 cmd.append("-w")
T.R. Fullharta28acc62013-03-18 10:31:26 -0700458 cmd.extend([key + OPTIONS.public_key_suffix,
459 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700460 input_name, sign_name])
461
462 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700463 if password is not None:
464 password += "\n"
465 p.communicate(password)
466 if p.returncode != 0:
467 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
468
469 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700470 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700471 p.communicate()
472 if p.returncode != 0:
473 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
474 temp.close()
475
476
Doug Zongker37974732010-09-16 17:44:38 -0700477def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700478 """Check the data string passed against the max size limit, if
479 any, for the given target. Raise exception if the data is too big.
480 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700481
Doug Zongker1684d9c2010-09-17 07:44:38 -0700482 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700483 mount_point = "/" + target
484
Ying Wang2a386e02014-06-03 14:07:27 -0700485 fs_type = None
486 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700487 if info_dict["fstab"]:
488 if mount_point == "/userdata": mount_point = "/data"
489 p = info_dict["fstab"][mount_point]
490 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800491 device = p.device
492 if "/" in device:
493 device = device[device.rfind("/")+1:]
494 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700495 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700496
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700497 if fs_type == "yaffs2":
498 # image size should be increased by 1/64th to account for the
499 # spare area (64 bytes per 2k page)
500 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800501 size = len(data)
502 pct = float(size) * 100.0 / limit
503 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
504 if pct >= 99.0:
505 raise ExternalError(msg)
506 elif pct >= 95.0:
507 print
508 print " WARNING: ", msg
509 print
510 elif OPTIONS.verbose:
511 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700512
513
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800514def ReadApkCerts(tf_zip):
515 """Given a target_files ZipFile, parse the META/apkcerts.txt file
516 and return a {package: cert} dict."""
517 certmap = {}
518 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
519 line = line.strip()
520 if not line: continue
521 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
522 r'private_key="(.*)"$', line)
523 if m:
524 name, cert, privkey = m.groups()
T.R. Fullharta28acc62013-03-18 10:31:26 -0700525 public_key_suffix_len = len(OPTIONS.public_key_suffix)
526 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800527 if cert in SPECIAL_CERT_STRINGS and not privkey:
528 certmap[name] = cert
T.R. Fullharta28acc62013-03-18 10:31:26 -0700529 elif (cert.endswith(OPTIONS.public_key_suffix) and
530 privkey.endswith(OPTIONS.private_key_suffix) and
531 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
532 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800533 else:
534 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
535 return certmap
536
537
Doug Zongkereef39442009-04-02 12:14:19 -0700538COMMON_DOCSTRING = """
539 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700540 Prepend <dir>/bin to the list of places to search for binaries
541 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700542
Doug Zongker05d3dea2009-06-22 11:32:31 -0700543 -s (--device_specific) <file>
544 Path to the python module containing device-specific
545 releasetools code.
546
Doug Zongker8bec09e2009-11-30 15:37:14 -0800547 -x (--extra) <key=value>
548 Add a key/value pair to the 'extras' dict, which device-specific
549 extension code may look at.
550
Doug Zongkereef39442009-04-02 12:14:19 -0700551 -v (--verbose)
552 Show command lines being executed.
553
554 -h (--help)
555 Display this usage message and exit.
556"""
557
558def Usage(docstring):
559 print docstring.rstrip("\n")
560 print COMMON_DOCSTRING
561
562
563def ParseOptions(argv,
564 docstring,
565 extra_opts="", extra_long_opts=(),
566 extra_option_handler=None):
567 """Parse the options in argv and return any arguments that aren't
568 flags. docstring is the calling module's docstring, to be displayed
569 for errors and -h. extra_opts and extra_long_opts are for flags
570 defined by the caller, which are processed by passing them to
571 extra_option_handler."""
572
573 try:
574 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800575 argv, "hvp:s:x:" + extra_opts,
T.R. Fullharta28acc62013-03-18 10:31:26 -0700576 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
577 "java_path=", "public_key_suffix=", "private_key_suffix=",
578 "device_specific=", "extra="] +
579 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700580 except getopt.GetoptError, err:
581 Usage(docstring)
582 print "**", str(err), "**"
583 sys.exit(2)
584
585 path_specified = False
586
587 for o, a in opts:
588 if o in ("-h", "--help"):
589 Usage(docstring)
590 sys.exit()
591 elif o in ("-v", "--verbose"):
592 OPTIONS.verbose = True
593 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700594 OPTIONS.search_path = a
T.R. Fullharta28acc62013-03-18 10:31:26 -0700595 elif o in ("--signapk_path",):
596 OPTIONS.signapk_path = a
597 elif o in ("--extra_signapk_args",):
598 OPTIONS.extra_signapk_args = shlex.split(a)
599 elif o in ("--java_path",):
600 OPTIONS.java_path = a
601 elif o in ("--public_key_suffix",):
602 OPTIONS.public_key_suffix = a
603 elif o in ("--private_key_suffix",):
604 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700605 elif o in ("-s", "--device_specific"):
606 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800607 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800608 key, value = a.split("=", 1)
609 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700610 else:
611 if extra_option_handler is None or not extra_option_handler(o, a):
612 assert False, "unknown option \"%s\"" % (o,)
613
Doug Zongker602a84e2009-06-18 08:35:12 -0700614 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
615 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700616
617 return args
618
619
620def Cleanup():
621 for i in OPTIONS.tempfiles:
622 if os.path.isdir(i):
623 shutil.rmtree(i)
624 else:
625 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700626
627
628class PasswordManager(object):
629 def __init__(self):
630 self.editor = os.getenv("EDITOR", None)
631 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
632
633 def GetPasswords(self, items):
634 """Get passwords corresponding to each string in 'items',
635 returning a dict. (The dict may have keys in addition to the
636 values in 'items'.)
637
638 Uses the passwords in $ANDROID_PW_FILE if available, letting the
639 user edit that file to add more needed passwords. If no editor is
640 available, or $ANDROID_PW_FILE isn't define, prompts the user
641 interactively in the ordinary way.
642 """
643
644 current = self.ReadFile()
645
646 first = True
647 while True:
648 missing = []
649 for i in items:
650 if i not in current or not current[i]:
651 missing.append(i)
652 # Are all the passwords already in the file?
653 if not missing: return current
654
655 for i in missing:
656 current[i] = ""
657
658 if not first:
659 print "key file %s still missing some passwords." % (self.pwfile,)
660 answer = raw_input("try to edit again? [y]> ").strip()
661 if answer and answer[0] not in 'yY':
662 raise RuntimeError("key passwords unavailable")
663 first = False
664
665 current = self.UpdateAndReadFile(current)
666
667 def PromptResult(self, current):
668 """Prompt the user to enter a value (password) for each key in
669 'current' whose value is fales. Returns a new dict with all the
670 values.
671 """
672 result = {}
673 for k, v in sorted(current.iteritems()):
674 if v:
675 result[k] = v
676 else:
677 while True:
678 result[k] = getpass.getpass("Enter password for %s key> "
679 % (k,)).strip()
680 if result[k]: break
681 return result
682
683 def UpdateAndReadFile(self, current):
684 if not self.editor or not self.pwfile:
685 return self.PromptResult(current)
686
687 f = open(self.pwfile, "w")
688 os.chmod(self.pwfile, 0600)
689 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
690 f.write("# (Additional spaces are harmless.)\n\n")
691
692 first_line = None
693 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
694 sorted.sort()
695 for i, (_, k, v) in enumerate(sorted):
696 f.write("[[[ %s ]]] %s\n" % (v, k))
697 if not v and first_line is None:
698 # position cursor on first line with no password.
699 first_line = i + 4
700 f.close()
701
702 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
703 _, _ = p.communicate()
704
705 return self.ReadFile()
706
707 def ReadFile(self):
708 result = {}
709 if self.pwfile is None: return result
710 try:
711 f = open(self.pwfile, "r")
712 for line in f:
713 line = line.strip()
714 if not line or line[0] == '#': continue
715 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
716 if not m:
717 print "failed to parse password file: ", line
718 else:
719 result[m.group(2)] = m.group(1)
720 f.close()
721 except IOError, e:
722 if e.errno != errno.ENOENT:
723 print "error reading password file: ", str(e)
724 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700725
726
727def ZipWriteStr(zip, filename, data, perms=0644):
728 # use a fixed timestamp so the output is repeatable.
729 zinfo = zipfile.ZipInfo(filename=filename,
730 date_time=(2009, 1, 1, 0, 0, 0))
731 zinfo.compress_type = zip.compression
732 zinfo.external_attr = perms << 16
733 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700734
735
736class DeviceSpecificParams(object):
737 module = None
738 def __init__(self, **kwargs):
739 """Keyword arguments to the constructor become attributes of this
740 object, which is passed to all functions in the device-specific
741 module."""
742 for k, v in kwargs.iteritems():
743 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800744 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700745
746 if self.module is None:
747 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700748 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700749 try:
750 if os.path.isdir(path):
751 info = imp.find_module("releasetools", [path])
752 else:
753 d, f = os.path.split(path)
754 b, x = os.path.splitext(f)
755 if x == ".py":
756 f = b
757 info = imp.find_module(f, [d])
758 self.module = imp.load_module("device_specific", *info)
759 except ImportError:
760 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700761
762 def _DoCall(self, function_name, *args, **kwargs):
763 """Call the named function in the device-specific module, passing
764 the given args and kwargs. The first argument to the call will be
765 the DeviceSpecific object itself. If there is no module, or the
766 module does not define the function, return the value of the
767 'default' kwarg (which itself defaults to None)."""
768 if self.module is None or not hasattr(self.module, function_name):
769 return kwargs.get("default", None)
770 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
771
772 def FullOTA_Assertions(self):
773 """Called after emitting the block of assertions at the top of a
774 full OTA package. Implementations can add whatever additional
775 assertions they like."""
776 return self._DoCall("FullOTA_Assertions")
777
Doug Zongkere5ff5902012-01-17 10:55:37 -0800778 def FullOTA_InstallBegin(self):
779 """Called at the start of full OTA installation."""
780 return self._DoCall("FullOTA_InstallBegin")
781
Doug Zongker05d3dea2009-06-22 11:32:31 -0700782 def FullOTA_InstallEnd(self):
783 """Called at the end of full OTA installation; typically this is
784 used to install the image for the device's baseband processor."""
785 return self._DoCall("FullOTA_InstallEnd")
786
787 def IncrementalOTA_Assertions(self):
788 """Called after emitting the block of assertions at the top of an
789 incremental OTA package. Implementations can add whatever
790 additional assertions they like."""
791 return self._DoCall("IncrementalOTA_Assertions")
792
Doug Zongkere5ff5902012-01-17 10:55:37 -0800793 def IncrementalOTA_VerifyBegin(self):
794 """Called at the start of the verification phase of incremental
795 OTA installation; additional checks can be placed here to abort
796 the script before any changes are made."""
797 return self._DoCall("IncrementalOTA_VerifyBegin")
798
Doug Zongker05d3dea2009-06-22 11:32:31 -0700799 def IncrementalOTA_VerifyEnd(self):
800 """Called at the end of the verification phase of incremental OTA
801 installation; additional checks can be placed here to abort the
802 script before any changes are made."""
803 return self._DoCall("IncrementalOTA_VerifyEnd")
804
Doug Zongkere5ff5902012-01-17 10:55:37 -0800805 def IncrementalOTA_InstallBegin(self):
806 """Called at the start of incremental OTA installation (after
807 verification is complete)."""
808 return self._DoCall("IncrementalOTA_InstallBegin")
809
Doug Zongker05d3dea2009-06-22 11:32:31 -0700810 def IncrementalOTA_InstallEnd(self):
811 """Called at the end of incremental OTA installation; typically
812 this is used to install the image for the device's baseband
813 processor."""
814 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700815
816class File(object):
817 def __init__(self, name, data):
818 self.name = name
819 self.data = data
820 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800821 self.sha1 = sha1(data).hexdigest()
822
823 @classmethod
824 def FromLocalFile(cls, name, diskname):
825 f = open(diskname, "rb")
826 data = f.read()
827 f.close()
828 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700829
830 def WriteToTemp(self):
831 t = tempfile.NamedTemporaryFile()
832 t.write(self.data)
833 t.flush()
834 return t
835
836 def AddToZip(self, z):
837 ZipWriteStr(z, self.name, self.data)
838
839DIFF_PROGRAM_BY_EXT = {
840 ".gz" : "imgdiff",
841 ".zip" : ["imgdiff", "-z"],
842 ".jar" : ["imgdiff", "-z"],
843 ".apk" : ["imgdiff", "-z"],
844 ".img" : "imgdiff",
845 }
846
847class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700848 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700849 self.tf = tf
850 self.sf = sf
851 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700852 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700853
854 def ComputePatch(self):
855 """Compute the patch (as a string of data) needed to turn sf into
856 tf. Returns the same tuple as GetPatch()."""
857
858 tf = self.tf
859 sf = self.sf
860
Doug Zongker24cd2802012-08-14 16:36:15 -0700861 if self.diff_program:
862 diff_program = self.diff_program
863 else:
864 ext = os.path.splitext(tf.name)[1]
865 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700866
867 ttemp = tf.WriteToTemp()
868 stemp = sf.WriteToTemp()
869
870 ext = os.path.splitext(tf.name)[1]
871
872 try:
873 ptemp = tempfile.NamedTemporaryFile()
874 if isinstance(diff_program, list):
875 cmd = copy.copy(diff_program)
876 else:
877 cmd = [diff_program]
878 cmd.append(stemp.name)
879 cmd.append(ttemp.name)
880 cmd.append(ptemp.name)
881 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
882 _, err = p.communicate()
883 if err or p.returncode != 0:
884 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
885 return None
886 diff = ptemp.read()
887 finally:
888 ptemp.close()
889 stemp.close()
890 ttemp.close()
891
892 self.patch = diff
893 return self.tf, self.sf, self.patch
894
895
896 def GetPatch(self):
897 """Return a tuple (target_file, source_file, patch_data).
898 patch_data may be None if ComputePatch hasn't been called, or if
899 computing the patch failed."""
900 return self.tf, self.sf, self.patch
901
902
903def ComputeDifferences(diffs):
904 """Call ComputePatch on all the Difference objects in 'diffs'."""
905 print len(diffs), "diffs to compute"
906
907 # Do the largest files first, to try and reduce the long-pole effect.
908 by_size = [(i.tf.size, i) for i in diffs]
909 by_size.sort(reverse=True)
910 by_size = [i[1] for i in by_size]
911
912 lock = threading.Lock()
913 diff_iter = iter(by_size) # accessed under lock
914
915 def worker():
916 try:
917 lock.acquire()
918 for d in diff_iter:
919 lock.release()
920 start = time.time()
921 d.ComputePatch()
922 dur = time.time() - start
923 lock.acquire()
924
925 tf, sf, patch = d.GetPatch()
926 if sf.name == tf.name:
927 name = tf.name
928 else:
929 name = "%s (%s)" % (tf.name, sf.name)
930 if patch is None:
931 print "patching failed! %s" % (name,)
932 else:
933 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
934 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
935 lock.release()
936 except Exception, e:
937 print e
938 raise
939
940 # start worker threads; wait for them all to finish.
941 threads = [threading.Thread(target=worker)
942 for i in range(OPTIONS.worker_threads)]
943 for th in threads:
944 th.start()
945 while threads:
946 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700947
948
949# map recovery.fstab's fs_types to mount/format "partition types"
950PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
951 "ext4": "EMMC", "emmc": "EMMC" }
952
953def GetTypeAndDevice(mount_point, info):
954 fstab = info["fstab"]
955 if fstab:
956 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
957 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800958 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +0000959
960
961def ParseCertificate(data):
962 """Parse a PEM-format certificate."""
963 cert = []
964 save = False
965 for line in data.split("\n"):
966 if "--END CERTIFICATE--" in line:
967 break
968 if save:
969 cert.append(line)
970 if "--BEGIN CERTIFICATE--" in line:
971 save = True
972 cert = "".join(cert).decode('base64')
973 return cert