From 20ddc6d2486203fa2a79e6bbbe25e20a305da218 Mon Sep 17 00:00:00 2001 From: Hsin-Yi Chen Date: Tue, 18 Feb 2020 18:52:58 +0800 Subject: [PATCH] Add repack_super_image Bug: 148839595 Test: make dist gsi_utils Test: repack_super_image \ --ota-tools $ANDROID_PRODUCT_OUT/otatools.zip \ --misc-info ./misc_info.txt \ ./super.img product= \ system=$ANDROID_PRODUCT_OUT/system.img Change-Id: I29ff8c6f0e117d3b42cc975929bd0a0823136bc9 --- gsi/repack_super_image/Android.bp | 33 +++ gsi/repack_super_image/repack_super_image.py | 206 +++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 gsi/repack_super_image/Android.bp create mode 100644 gsi/repack_super_image/repack_super_image.py diff --git a/gsi/repack_super_image/Android.bp b/gsi/repack_super_image/Android.bp new file mode 100644 index 000000000..935b76bb3 --- /dev/null +++ b/gsi/repack_super_image/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 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. + +python_binary_host { + name: "repack_super_image", + main: "repack_super_image.py", + srcs: [ + "repack_super_image.py", + ], + version: { + py2: { + enabled: false, + }, + py3: { + enabled: true, + embedded_launcher: true, + }, + }, + dist: { + targets: ["gsi_utils"], + }, +} diff --git a/gsi/repack_super_image/repack_super_image.py b/gsi/repack_super_image/repack_super_image.py new file mode 100644 index 000000000..235c37d5b --- /dev/null +++ b/gsi/repack_super_image/repack_super_image.py @@ -0,0 +1,206 @@ +#!/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(part_img_dict, lpmake_path, input_file, output_file): + """Changes the lpmake path and image paths in a misc info file.""" + 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 + output_file.write(line) + + for partition_name in partition_names: + if partition_name not in part_img_dict: + raise KeyError("No image for " + partition_name + " partition.") + img_path = part_img_dict[partition_name] + 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. If IMAGE_PATH is " + "empty, the partition will be resized to 0.") + args = parser.parse_args() + + # Convert the args.part_imgs to a dictionary. + part_img_dict = dict() + for part, img in args.part_imgs: + if part in part_img_dict: + raise ValueError(part + " partition is repeated.") + part_img_dict[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.") + 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)] + if part not in part_img_dict: + part_img_dict[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(part_img_dict, lpmake_path, misc_info, + misc_info_file) + + # Check that all input partitions are in the partition list. + parts_not_found = set(part for part, _ in args.part_imgs) - 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()