platform_build/tools/releasetools/add_img_to_target_files.py
David Zeuthen d995f4b04d Add support for Brillo Verified Boot.
The following variables are introduced

 BOARD_BVB_ENABLE: can be set to true to build boot.img and system.img
 files compatible with Brillo Verfied Boot.

 BOARD_BVB_ROLLBACK_INDEX: can be set to an integer to use for the
 rollback index.

 BOARD_BVB_KEY_PATH, BOARD_BVB_ALGORITHM: If set, the former must be a
 path to the private key used to sign the boot image and the latter must
 be the algorithm to use. If unset, a test-key stored in the tree will
 be used.

 BOARD_BVB_MAKE_BOOT_IMAGE_ARGS: Extra options to pass to 'bvbtool
 make_boot_image'.

 BOARD_BVB_SIGN_BOOT_IMAGE_ARGS: Extra options to pass to 'bvbtool
 sign_boot_image'.

 BOARD_BVB_ADD_IMAGE_HASHES_ARGS: Extra options to pass to 'bvbtool
 add_image_hashes'.

 BOARD_CUSTOM_BVBTOOL: Can be set to specify what bvbtool program to
 use.

The existing BOARD_KERNEL_CMDLINE variable is also used, as are existing
kernel and initrd-related variables. Therefore, simply adding

 BOARD_BVB_ENABLE := true

to an existing Makefile should do the trick.

Bug: 26185038
TEST=Added 'BOARD_BVB_ENABLE := true' to hardware/bsp/intel/soc/edison/soc.mk
  and built an image and then ran bvbtool's info_boot_image and
  info_image_hashes commands on the resulting boot.img and system.img
  files and verified that the information was correct. Also ran 'm dist'
  and verified that the boot.img and system.img files in the resulting
  target_files.zip file had similar information.

Change-Id: I08045ed8b0cbddc7c3acdd3a6f2c4bb75cb44bbc
2016-03-18 15:25:43 -04:00

427 lines
14 KiB
Python
Executable file

#!/usr/bin/env python
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Given a target-files zipfile that does not contain images (ie, does
not have an IMAGES/ top-level subdirectory), produce the images and
add them to the zipfile.
Usage: add_img_to_target_files target_files
"""
import sys
if sys.hexversion < 0x02070000:
print >> sys.stderr, "Python 2.7 or newer is required."
sys.exit(1)
import datetime
import errno
import os
import shlex
import shutil
import subprocess
import tempfile
import zipfile
import build_image
import common
OPTIONS = common.OPTIONS
OPTIONS.add_missing = False
OPTIONS.rebuild_recovery = False
OPTIONS.replace_verity_public_key = False
OPTIONS.replace_verity_private_key = False
OPTIONS.verity_signer_path = None
def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None):
"""Turn the contents of SYSTEM into a system image and store it in
output_zip. Returns the name of the system image file."""
prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img")
if os.path.exists(prebuilt_path):
print "system.img already exists in %s, no need to rebuild..." % (prefix,)
return prebuilt_path
def output_sink(fn, data):
ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w")
ofile.write(data)
ofile.close()
if OPTIONS.rebuild_recovery:
print "Building new recovery patch"
common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
boot_img, info_dict=OPTIONS.info_dict)
block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map")
imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict,
block_list=block_list)
# If requested, calculate and add dm-verity integrity hashes and
# metadata to system.img.
if OPTIONS.info_dict.get("board_bvb_enable", None) == "true":
bvbtool = os.getenv('BVBTOOL') or "bvbtool"
cmd = [bvbtool, "add_image_hashes", "--image", imgname]
args = OPTIONS.info_dict.get("board_bvb_add_image_hashes_args", None)
if args and args.strip():
cmd.extend(shlex.split(args))
p = common.Run(cmd, stdout=subprocess.PIPE)
p.communicate()
assert p.returncode == 0, "bvbtool add_image_hashes of %s image failed" % (
os.path.basename(OPTIONS.input_tmp),)
common.ZipWrite(output_zip, imgname, prefix + "system.img")
common.ZipWrite(output_zip, block_list, prefix + "system.map")
return imgname
def BuildSystem(input_dir, info_dict, block_list=None):
"""Build the (sparse) system image and return the name of a temp
file containing it."""
return CreateImage(input_dir, info_dict, "system", block_list=block_list)
def AddVendor(output_zip, prefix="IMAGES/"):
"""Turn the contents of VENDOR into a vendor image and store in it
output_zip."""
prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "vendor.img")
if os.path.exists(prebuilt_path):
print "vendor.img already exists in %s, no need to rebuild..." % (prefix,)
return
block_list = common.MakeTempFile(prefix="vendor-blocklist-", suffix=".map")
imgname = BuildVendor(OPTIONS.input_tmp, OPTIONS.info_dict,
block_list=block_list)
common.ZipWrite(output_zip, imgname, prefix + "vendor.img")
common.ZipWrite(output_zip, block_list, prefix + "vendor.map")
def BuildVendor(input_dir, info_dict, block_list=None):
"""Build the (sparse) vendor image and return the name of a temp
file containing it."""
return CreateImage(input_dir, info_dict, "vendor", block_list=block_list)
def CreateImage(input_dir, info_dict, what, block_list=None):
print "creating " + what + ".img..."
img = common.MakeTempFile(prefix=what + "-", suffix=".img")
# The name of the directory it is making an image out of matters to
# mkyaffs2image. It wants "system" but we have a directory named
# "SYSTEM", so create a symlink.
try:
os.symlink(os.path.join(input_dir, what.upper()),
os.path.join(input_dir, what))
except OSError as e:
# bogus error on my mac version?
# File "./build/tools/releasetools/img_from_target_files"
# os.path.join(OPTIONS.input_tmp, "system"))
# OSError: [Errno 17] File exists
if e.errno == errno.EEXIST:
pass
image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
fstab = info_dict["fstab"]
if fstab:
image_props["fs_type"] = fstab["/" + what].fs_type
# Use a fixed timestamp (01/01/2009) when packaging the image.
# Bug: 24377993
epoch = datetime.datetime.fromtimestamp(0)
timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
image_props["timestamp"] = int(timestamp)
if what == "system":
fs_config_prefix = ""
else:
fs_config_prefix = what + "_"
fs_config = os.path.join(
input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
if not os.path.exists(fs_config):
fs_config = None
# Override values loaded from info_dict.
if fs_config:
image_props["fs_config"] = fs_config
if block_list:
image_props["block_list"] = block_list
succ = build_image.BuildImage(os.path.join(input_dir, what),
image_props, img)
assert succ, "build " + what + ".img image failed"
return img
def AddUserdata(output_zip, prefix="IMAGES/"):
"""Create a userdata image and store it in output_zip.
In most case we just create and store an empty userdata.img;
But the invoker can also request to create userdata.img with real
data from the target files, by setting "userdata_img_with_data=true"
in OPTIONS.info_dict.
"""
prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "userdata.img")
if os.path.exists(prebuilt_path):
print "userdata.img already exists in %s, no need to rebuild..." % (prefix,)
return
image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data")
# We only allow yaffs to have a 0/missing partition_size.
# Extfs, f2fs must have a size. Skip userdata.img if no size.
if (not image_props.get("fs_type", "").startswith("yaffs") and
not image_props.get("partition_size")):
return
print "creating userdata.img..."
# Use a fixed timestamp (01/01/2009) when packaging the image.
# Bug: 24377993
epoch = datetime.datetime.fromtimestamp(0)
timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
image_props["timestamp"] = int(timestamp)
# The name of the directory it is making an image out of matters to
# mkyaffs2image. So we create a temp dir, and within it we create an
# empty dir named "data", or a symlink to the DATA dir,
# and build the image from that.
temp_dir = tempfile.mkdtemp()
user_dir = os.path.join(temp_dir, "data")
empty = (OPTIONS.info_dict.get("userdata_img_with_data") != "true")
if empty:
# Create an empty dir.
os.mkdir(user_dir)
else:
# Symlink to the DATA dir.
os.symlink(os.path.join(OPTIONS.input_tmp, "DATA"),
user_dir)
img = tempfile.NamedTemporaryFile()
fstab = OPTIONS.info_dict["fstab"]
if fstab:
image_props["fs_type"] = fstab["/data"].fs_type
succ = build_image.BuildImage(user_dir, image_props, img.name)
assert succ, "build userdata.img image failed"
common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
common.ZipWrite(output_zip, img.name, prefix + "userdata.img")
img.close()
shutil.rmtree(temp_dir)
def AddCache(output_zip, prefix="IMAGES/"):
"""Create an empty cache image and store it in output_zip."""
prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "cache.img")
if os.path.exists(prebuilt_path):
print "cache.img already exists in %s, no need to rebuild..." % (prefix,)
return
image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache")
# The build system has to explicitly request for cache.img.
if "fs_type" not in image_props:
return
print "creating cache.img..."
# Use a fixed timestamp (01/01/2009) when packaging the image.
# Bug: 24377993
epoch = datetime.datetime.fromtimestamp(0)
timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
image_props["timestamp"] = int(timestamp)
# The name of the directory it is making an image out of matters to
# mkyaffs2image. So we create a temp dir, and within it we create an
# empty dir named "cache", and build the image from that.
temp_dir = tempfile.mkdtemp()
user_dir = os.path.join(temp_dir, "cache")
os.mkdir(user_dir)
img = tempfile.NamedTemporaryFile()
fstab = OPTIONS.info_dict["fstab"]
if fstab:
image_props["fs_type"] = fstab["/cache"].fs_type
succ = build_image.BuildImage(user_dir, image_props, img.name)
assert succ, "build cache.img image failed"
common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
common.ZipWrite(output_zip, img.name, prefix + "cache.img")
img.close()
os.rmdir(user_dir)
os.rmdir(temp_dir)
def AddImagesToTargetFiles(filename):
OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename)
if not OPTIONS.add_missing:
for n in input_zip.namelist():
if n.startswith("IMAGES/"):
print "target_files appears to already contain images."
sys.exit(1)
try:
input_zip.getinfo("VENDOR/")
has_vendor = True
except KeyError:
has_vendor = False
OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.input_tmp)
common.ZipClose(input_zip)
output_zip = zipfile.ZipFile(filename, "a",
compression=zipfile.ZIP_DEFLATED)
has_recovery = (OPTIONS.info_dict.get("no_recovery") != "true")
system_root_image = (OPTIONS.info_dict.get("system_root_image", None) == "true")
board_bvb_enable = (OPTIONS.info_dict.get("board_bvb_enable", None) == "true")
# Brillo Verified Boot is incompatible with certain
# configurations. Explicitly check for these.
if board_bvb_enable:
assert not has_recovery, "has_recovery incompatible with bvb"
assert not system_root_image, "system_root_image incompatible with bvb"
assert not OPTIONS.rebuild_recovery, "rebuild_recovery incompatible with bvb"
assert not has_vendor, "VENDOR images currently incompatible with bvb"
def banner(s):
print "\n\n++++ " + s + " ++++\n\n"
prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img")
boot_image = None
if os.path.exists(prebuilt_path):
banner("boot")
print "boot.img already exists in IMAGES/, no need to rebuild..."
if OPTIONS.rebuild_recovery:
boot_image = common.GetBootableImage(
"IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
else:
if board_bvb_enable:
# With Brillo Verified Boot, we need to build system.img before
# boot.img since the latter includes the dm-verity root hash and
# salt for the former.
pass
else:
banner("boot")
boot_image = common.GetBootableImage(
"IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
if boot_image:
boot_image.AddToZip(output_zip)
recovery_image = None
if has_recovery:
banner("recovery")
prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "recovery.img")
if os.path.exists(prebuilt_path):
print "recovery.img already exists in IMAGES/, no need to rebuild..."
if OPTIONS.rebuild_recovery:
recovery_image = common.GetBootableImage(
"IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp,
"RECOVERY")
else:
recovery_image = common.GetBootableImage(
"IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
if recovery_image:
recovery_image.AddToZip(output_zip)
banner("system")
system_img_path = AddSystem(
output_zip, recovery_img=recovery_image, boot_img=boot_image)
if OPTIONS.info_dict.get("board_bvb_enable", None) == "true":
# If we're using Brillo Verified Boot, we can now build boot.img
# given that we have system.img.
banner("boot")
boot_image = common.GetBootableImage(
"IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT",
system_img_path=system_img_path)
if boot_image:
boot_image.AddToZip(output_zip)
if has_vendor:
banner("vendor")
AddVendor(output_zip)
banner("userdata")
AddUserdata(output_zip)
banner("cache")
AddCache(output_zip)
# For devices using A/B update, copy over images from RADIO/ to IMAGES/ and
# make sure we have all the needed images ready under IMAGES/.
ab_partitions = os.path.join(OPTIONS.input_tmp, "META", "ab_partitions.txt")
if os.path.exists(ab_partitions):
with open(ab_partitions, 'r') as f:
lines = f.readlines()
for line in lines:
img_name = line.strip() + ".img"
img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
if os.path.exists(img_radio_path):
common.ZipWrite(output_zip, img_radio_path,
os.path.join("IMAGES", img_name))
# Zip spec says: All slashes MUST be forward slashes.
img_path = 'IMAGES/' + img_name
assert img_path in output_zip.namelist(), "cannot find " + img_name
common.ZipClose(output_zip)
def main(argv):
def option_handler(o, a):
if o in ("-a", "--add_missing"):
OPTIONS.add_missing = True
elif o in ("-r", "--rebuild_recovery",):
OPTIONS.rebuild_recovery = True
elif o == "--replace_verity_private_key":
OPTIONS.replace_verity_private_key = (True, a)
elif o == "--replace_verity_public_key":
OPTIONS.replace_verity_public_key = (True, a)
elif o == "--verity_signer_path":
OPTIONS.verity_signer_path = a
else:
return False
return True
args = common.ParseOptions(
argv, __doc__, extra_opts="ar",
extra_long_opts=["add_missing", "rebuild_recovery",
"replace_verity_public_key=",
"replace_verity_private_key=",
"verity_signer_path="],
extra_option_handler=option_handler)
if len(args) != 1:
common.Usage(__doc__)
sys.exit(1)
AddImagesToTargetFiles(args[0])
print "done."
if __name__ == '__main__':
try:
common.CloseInheritedPipes()
main(sys.argv[1:])
except common.ExternalError as e:
print
print " ERROR: %s" % (e,)
print
sys.exit(1)
finally:
common.Cleanup()