blob: c81eb0558adc03061cddccd07e1188b6a2608076 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2020 - 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.
"""This script is for test infrastructure to mix images in a super image."""
import argparse
import os
import shutil
import stat
import subprocess
import tempfile
import zipfile
# The file extension of the unpacked images.
IMG_FILE_EXT = ".img"
# The directory containing executable files in OTA tools zip.
BIN_DIR_NAME = "bin"
def existing_abs_path(path):
"""Validates that a path exists and returns the absolute path."""
abs_path = os.path.abspath(path)
if not os.path.exists(abs_path):
raise ValueError(path + " does not exist.")
return abs_path
def partition_image(part_img):
"""Splits a string into a pair of strings by "="."""
part, sep, img = part_img.partition("=")
if not part or not sep:
raise ValueError(part_img + " is not in the format of "
"PARITITON_NAME=IMAGE_PATH.")
return part, (existing_abs_path(img) if img else "")
def unzip_ota_tools(ota_tools_zip, output_dir):
"""Unzips OTA tools and sets the files in bin/ to be executable."""
ota_tools_zip.extractall(output_dir)
permissions = (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
for root_dir, dir_names, file_names in os.walk(
os.path.join(output_dir, BIN_DIR_NAME)):
for file_name in file_names:
file_path = os.path.join(root_dir, file_name)
file_stat = os.stat(file_path)
os.chmod(file_path, file_stat.st_mode | permissions)
def is_sparse_image(image_path):
"""Checks whether a file is a sparse image."""
with open(image_path, "rb") as image_file:
return image_file.read(4) == b"\x3a\xff\x26\xed"
def rewrite_misc_info(args_part_imgs, unpacked_part_imgs, lpmake_path,
input_file, output_file):
"""Changes the lpmake path and image paths in a misc info file.
Args:
args_part_imgs: A dict of {partition_name: image_path} that the user
intends to substitute. The partition_names must not have
slot suffixes.
unpacked_part_imgs: A dict of {partition_name: image_path} unpacked from
the input super image. The partition_names must have
slot suffixes if the misc info enables virtual_ab.
lpmake_path: The path to the lpmake binary.
input_file: The input misc info file object.
output_file: The output misc info file object.
Returns:
The list of the partition names without slot suffixes.
"""
virtual_ab = False
partition_names = ()
for line in input_file:
split_line = line.strip().split("=", 1)
if len(split_line) < 2:
split_line = (split_line[0], "")
if split_line[0] == "dynamic_partition_list":
partition_names = split_line[1].split()
elif split_line[0] == "lpmake":
output_file.write("lpmake=%s\n" % lpmake_path)
continue
elif split_line[0].endswith("_image"):
continue
elif split_line[0] == "virtual_ab" and split_line[1] == "true":
virtual_ab = True
output_file.write(line)
for partition_name in partition_names:
img_path = args_part_imgs.get(partition_name)
if img_path is None:
# _a is the active slot for the super images built from source.
img_path = unpacked_part_imgs.get(partition_name + "_a" if virtual_ab
else partition_name)
if img_path is None:
raise KeyError("No image for " + partition_name + " partition.")
if img_path != "":
output_file.write("%s_image=%s\n" % (partition_name, img_path))
return partition_names
def main():
parser = argparse.ArgumentParser(
description="This script is for test infrastructure to mix images in a "
"super image.")
parser.add_argument("--temp-dir",
default=tempfile.gettempdir(),
type=existing_abs_path,
help="The directory where this script creates "
"temporary files.")
parser.add_argument("--ota-tools",
required=True,
type=existing_abs_path,
help="The path to the zip or directory containing OTA "
"tools.")
parser.add_argument("--misc-info",
required=True,
type=existing_abs_path,
help="The path to the misc info file.")
parser.add_argument("super_img",
metavar="SUPER_IMG",
type=existing_abs_path,
help="The path to the super image to be repacked.")
parser.add_argument("part_imgs",
metavar="PART_IMG",
nargs="*",
type=partition_image,
help="The partition and the image that will be added "
"to the super image. The format is "
"PARITITON_NAME=IMAGE_PATH. PARTITION_NAME must "
"not have slot suffix. If IMAGE_PATH is empty, the "
"partition will be resized to 0.")
args = parser.parse_args()
# Convert the args.part_imgs to a dictionary.
args_part_imgs = dict()
for part, img in args.part_imgs:
if part in args_part_imgs:
raise ValueError(part + " partition is repeated.")
args_part_imgs[part] = img
if args.temp_dir:
tempfile.tempdir = args.temp_dir
temp_dirs = []
temp_files = []
try:
if os.path.isdir(args.ota_tools):
ota_tools_dir = args.ota_tools
else:
print("Unzip OTA tools.")
temp_ota_tools_dir = tempfile.mkdtemp(prefix="ota_tools")
temp_dirs.append(temp_ota_tools_dir)
with zipfile.ZipFile(args.ota_tools) as ota_tools_zip:
unzip_ota_tools(ota_tools_zip, temp_ota_tools_dir)
ota_tools_dir = temp_ota_tools_dir
if not is_sparse_image(args.super_img):
super_img_path = args.super_img
else:
print("Convert to unsparsed super image.")
simg2img_path = os.path.join(ota_tools_dir, BIN_DIR_NAME, "simg2img")
with tempfile.NamedTemporaryFile(
mode="wb", prefix="super", suffix=".img",
delete=False) as raw_super_img:
temp_files.append(raw_super_img.name)
super_img_path = raw_super_img.name
subprocess.check_call([simg2img_path, args.super_img, super_img_path])
print("Unpack super image.")
unpacked_part_imgs = dict()
lpunpack_path = os.path.join(ota_tools_dir, BIN_DIR_NAME, "lpunpack")
unpack_dir = tempfile.mkdtemp(prefix="lpunpack")
temp_dirs.append(unpack_dir)
subprocess.check_call([lpunpack_path, super_img_path, unpack_dir])
for file_name in os.listdir(unpack_dir):
if file_name.endswith(IMG_FILE_EXT):
part = file_name[:-len(IMG_FILE_EXT)]
unpacked_part_imgs[part] = os.path.join(unpack_dir, file_name)
print("Create temporary misc info.")
lpmake_path = os.path.join(ota_tools_dir, BIN_DIR_NAME, "lpmake")
with tempfile.NamedTemporaryFile(
mode="w", prefix="misc_info", suffix=".txt",
delete=False) as misc_info_file:
temp_files.append(misc_info_file.name)
misc_info_file_path = misc_info_file.name
with open(args.misc_info, "r") as misc_info:
part_list = rewrite_misc_info(args_part_imgs, unpacked_part_imgs,
lpmake_path, misc_info, misc_info_file)
# Check that all input partitions are in the partition list.
parts_not_found = args_part_imgs.keys() - set(part_list)
if parts_not_found:
raise ValueError("Cannot find partitions in misc info: " +
" ".join(parts_not_found))
print("Build super image.")
build_super_image_path = os.path.join(ota_tools_dir, BIN_DIR_NAME,
"build_super_image")
subprocess.check_call([build_super_image_path, misc_info_file_path,
args.super_img])
finally:
for temp_dir in temp_dirs:
shutil.rmtree(temp_dir, ignore_errors=True)
for temp_file in temp_files:
os.remove(temp_file)
if __name__ == "__main__":
main()