401 lines
16 KiB
C++
401 lines
16 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "host/commands/assemble_cvd/boot_image_utils.h"
|
|
#include "host/libs/config/cuttlefish_config.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/strings.h>
|
|
|
|
#include "common/libs/utils/files.h"
|
|
#include "common/libs/utils/subprocess.h"
|
|
|
|
const char TMP_EXTENSION[] = ".tmp";
|
|
const char CPIO_EXT[] = ".cpio";
|
|
const char TMP_RD_DIR[] = "stripped_ramdisk_dir";
|
|
const char STRIPPED_RD[] = "stripped_ramdisk";
|
|
const char CONCATENATED_VENDOR_RAMDISK[] = "concatenated_vendor_ramdisk";
|
|
namespace cuttlefish {
|
|
namespace {
|
|
std::string ExtractValue(const std::string& dictionary, const std::string& key) {
|
|
std::size_t index = dictionary.find(key);
|
|
if (index != std::string::npos) {
|
|
std::size_t end_index = dictionary.find('\n', index + key.length());
|
|
if (end_index != std::string::npos) {
|
|
return dictionary.substr(index + key.length(),
|
|
end_index - index - key.length());
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Though it is just as fast to overwrite the existing boot images with the newly generated ones,
|
|
// the cuttlefish composite disk generator checks the age of each of the components and
|
|
// regenerates the disk outright IF any one of the components is younger/newer than the current
|
|
// composite disk. If this file overwrite occurs, that condition is fulfilled. This action then
|
|
// causes data in the userdata partition from previous boots to be lost (which is not expected by
|
|
// the user if they've been booting the same kernel/ramdisk combination repeatedly).
|
|
// Consequently, the file is checked for differences and ONLY overwritten if there is a diff.
|
|
bool DeleteTmpFileIfNotChanged(const std::string& tmp_file, const std::string& current_file) {
|
|
if (!FileExists(current_file) ||
|
|
ReadFile(current_file) != ReadFile(tmp_file)) {
|
|
if (!RenameFile(tmp_file, current_file)) {
|
|
LOG(ERROR) << "Unable to delete " << current_file;
|
|
return false;
|
|
}
|
|
LOG(DEBUG) << "Updated " << current_file;
|
|
} else {
|
|
LOG(DEBUG) << "Didn't update " << current_file;
|
|
RemoveFile(tmp_file);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
|
|
const std::string& original_ramdisk_path,
|
|
const std::string& new_ramdisk_path,
|
|
const std::string& build_dir) {
|
|
int success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") + " -c -d -l " +
|
|
original_ramdisk_path + " > " + original_ramdisk_path + CPIO_EXT});
|
|
CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
|
|
|
|
const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
|
|
success =
|
|
mkdir(ramdisk_stage_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
|
CHECK(success == 0) << "Could not mkdir \"" << ramdisk_stage_dir
|
|
<< "\", error was " << strerror(errno);
|
|
|
|
success = execute(
|
|
{"/bin/bash", "-c",
|
|
"(cd " + ramdisk_stage_dir + " && while " + HostBinaryPath("toybox") +
|
|
" cpio -idu; do :; done) < " + original_ramdisk_path + CPIO_EXT});
|
|
CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
|
|
<< success;
|
|
|
|
success = execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
|
|
CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
|
|
<< "Exited with status " << success;
|
|
|
|
const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
|
|
success = execute({"/bin/bash", "-c",
|
|
HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
|
|
" > " + stripped_ramdisk_path + CPIO_EXT});
|
|
CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
|
|
<< success;
|
|
|
|
success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") +
|
|
" -c -l -12 --favor-decSpeed " + stripped_ramdisk_path + CPIO_EXT + " > " +
|
|
stripped_ramdisk_path});
|
|
CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
|
|
|
|
// Concatenates the stripped ramdisk and input ramdisk and places the result at new_ramdisk_path
|
|
std::ofstream final_rd(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
|
|
std::ifstream ramdisk_a(stripped_ramdisk_path, std::ios_base::binary);
|
|
std::ifstream ramdisk_b(kernel_modules_ramdisk_path, std::ios_base::binary);
|
|
final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool UnpackBootImage(const std::string& boot_image_path,
|
|
const std::string& unpack_dir) {
|
|
auto unpack_path = HostBinaryPath("unpack_bootimg");
|
|
Command unpack_cmd(unpack_path);
|
|
unpack_cmd.AddParameter("--boot_img");
|
|
unpack_cmd.AddParameter(boot_image_path);
|
|
unpack_cmd.AddParameter("--out");
|
|
unpack_cmd.AddParameter(unpack_dir);
|
|
|
|
auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
|
|
if (!output_file->IsOpen()) {
|
|
LOG(ERROR) << "Unable to create intermediate boot params file: "
|
|
<< output_file->StrError();
|
|
return false;
|
|
}
|
|
unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
|
|
|
|
int success = unpack_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
|
|
<< success;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UnpackVendorBootImageIfNotUnpacked(
|
|
const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
|
|
// the vendor boot params file is created during the first unpack. If it's
|
|
// already there, a unpack has occurred and there's no need to repeat the
|
|
// process.
|
|
if (FileExists(unpack_dir + "/vendor_boot_params")) {
|
|
return true;
|
|
}
|
|
|
|
auto unpack_path = HostBinaryPath("unpack_bootimg");
|
|
Command unpack_cmd(unpack_path);
|
|
unpack_cmd.AddParameter("--boot_img");
|
|
unpack_cmd.AddParameter(vendor_boot_image_path);
|
|
unpack_cmd.AddParameter("--out");
|
|
unpack_cmd.AddParameter(unpack_dir);
|
|
auto output_file = SharedFD::Creat(unpack_dir + "/vendor_boot_params", 0666);
|
|
if (!output_file->IsOpen()) {
|
|
LOG(ERROR) << "Unable to create intermediate vendor boot params file: "
|
|
<< output_file->StrError();
|
|
return false;
|
|
}
|
|
unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
|
|
int success = unpack_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status " << success;
|
|
return false;
|
|
}
|
|
|
|
// Concatenates all vendor ramdisk into one single ramdisk.
|
|
Command concat_cmd("/bin/bash");
|
|
concat_cmd.AddParameter("-c");
|
|
concat_cmd.AddParameter("cat " + unpack_dir + "/vendor_ramdisk*");
|
|
auto concat_file =
|
|
SharedFD::Creat(unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK, 0666);
|
|
if (!concat_file->IsOpen()) {
|
|
LOG(ERROR) << "Unable to create concatenated vendor ramdisk file: "
|
|
<< concat_file->StrError();
|
|
return false;
|
|
}
|
|
concat_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, concat_file);
|
|
success = concat_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run cat. Exited with status " << success;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RepackBootImage(const std::string& new_kernel_path,
|
|
const std::string& boot_image_path,
|
|
const std::string& new_boot_image_path,
|
|
const std::string& build_dir) {
|
|
if (UnpackBootImage(boot_image_path, build_dir) == false) {
|
|
return false;
|
|
}
|
|
|
|
std::string boot_params = ReadFile(build_dir + "/boot_params");
|
|
auto kernel_cmdline = ExtractValue(boot_params, "command line args: ");
|
|
LOG(DEBUG) << "Cmdline from boot image is " << kernel_cmdline;
|
|
|
|
auto tmp_boot_image_path = new_boot_image_path + TMP_EXTENSION;
|
|
auto repack_path = HostBinaryPath("mkbootimg");
|
|
Command repack_cmd(repack_path);
|
|
repack_cmd.AddParameter("--kernel");
|
|
repack_cmd.AddParameter(new_kernel_path);
|
|
repack_cmd.AddParameter("--ramdisk");
|
|
repack_cmd.AddParameter(build_dir + "/ramdisk");
|
|
repack_cmd.AddParameter("--header_version");
|
|
repack_cmd.AddParameter("4");
|
|
repack_cmd.AddParameter("--cmdline");
|
|
repack_cmd.AddParameter(kernel_cmdline);
|
|
repack_cmd.AddParameter("-o");
|
|
repack_cmd.AddParameter(tmp_boot_image_path);
|
|
int success = repack_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
|
|
return false;
|
|
}
|
|
|
|
auto avbtool_path = HostBinaryPath("avbtool");
|
|
Command avb_cmd(avbtool_path);
|
|
avb_cmd.AddParameter("add_hash_footer");
|
|
avb_cmd.AddParameter("--image");
|
|
avb_cmd.AddParameter(tmp_boot_image_path);
|
|
avb_cmd.AddParameter("--partition_size");
|
|
avb_cmd.AddParameter(FileSize(boot_image_path));
|
|
avb_cmd.AddParameter("--partition_name");
|
|
avb_cmd.AddParameter("boot");
|
|
success = avb_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
|
|
return false;
|
|
}
|
|
|
|
return DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path);
|
|
}
|
|
|
|
bool RepackVendorBootImage(const std::string& new_ramdisk,
|
|
const std::string& vendor_boot_image_path,
|
|
const std::string& new_vendor_boot_image_path,
|
|
const std::string& unpack_dir,
|
|
bool bootconfig_supported) {
|
|
if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
|
|
false) {
|
|
return false;
|
|
}
|
|
|
|
std::string ramdisk_path;
|
|
if (new_ramdisk.size()) {
|
|
ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
|
|
if (!FileExists(ramdisk_path)) {
|
|
RepackVendorRamdisk(new_ramdisk,
|
|
unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
|
|
ramdisk_path, unpack_dir);
|
|
}
|
|
} else {
|
|
ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
|
|
}
|
|
|
|
std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
|
|
LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
|
|
<< bootconfig;
|
|
std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
|
|
auto kernel_cmdline =
|
|
ExtractValue(vendor_boot_params, "vendor command line args: ") +
|
|
(bootconfig_supported
|
|
? ""
|
|
: " " + android::base::StringReplace(bootconfig, "\n", " ", true));
|
|
if (!bootconfig_supported) {
|
|
// TODO(b/182417593): Until we pass the module parameters through
|
|
// modules.options, we pass them through bootconfig using
|
|
// 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
|
|
// rename them back to the old cmdline version
|
|
kernel_cmdline = android::base::StringReplace(
|
|
kernel_cmdline, " kernel.", " ", true);
|
|
}
|
|
LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
|
|
|
|
auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
|
|
auto repack_path = HostBinaryPath("mkbootimg");
|
|
Command repack_cmd(repack_path);
|
|
repack_cmd.AddParameter("--vendor_ramdisk");
|
|
repack_cmd.AddParameter(ramdisk_path);
|
|
repack_cmd.AddParameter("--header_version");
|
|
repack_cmd.AddParameter("4");
|
|
repack_cmd.AddParameter("--vendor_cmdline");
|
|
repack_cmd.AddParameter(kernel_cmdline);
|
|
repack_cmd.AddParameter("--vendor_boot");
|
|
repack_cmd.AddParameter(tmp_vendor_boot_image_path);
|
|
repack_cmd.AddParameter("--dtb");
|
|
repack_cmd.AddParameter(unpack_dir + "/dtb");
|
|
if (bootconfig_supported) {
|
|
repack_cmd.AddParameter("--vendor_bootconfig");
|
|
repack_cmd.AddParameter(unpack_dir + "/bootconfig");
|
|
}
|
|
|
|
int success = repack_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
|
|
return false;
|
|
}
|
|
|
|
auto avbtool_path = HostBinaryPath("avbtool");
|
|
Command avb_cmd(avbtool_path);
|
|
avb_cmd.AddParameter("add_hash_footer");
|
|
avb_cmd.AddParameter("--image");
|
|
avb_cmd.AddParameter(tmp_vendor_boot_image_path);
|
|
avb_cmd.AddParameter("--partition_size");
|
|
avb_cmd.AddParameter(FileSize(vendor_boot_image_path));
|
|
avb_cmd.AddParameter("--partition_name");
|
|
avb_cmd.AddParameter("vendor_boot");
|
|
success = avb_cmd.Start().Wait();
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
|
|
return false;
|
|
}
|
|
|
|
return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
|
|
}
|
|
|
|
bool RepackVendorBootImageWithEmptyRamdisk(
|
|
const std::string& vendor_boot_image_path,
|
|
const std::string& new_vendor_boot_image_path,
|
|
const std::string& unpack_dir, bool bootconfig_supported) {
|
|
auto empty_ramdisk_file =
|
|
SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
|
|
return RepackVendorBootImage(
|
|
unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
|
|
new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
|
|
}
|
|
|
|
void RepackGem5BootImage(const std::string& initrd_path,
|
|
const std::string& bootconfig_path,
|
|
const std::string& unpack_dir) {
|
|
// Simulate per-instance what the bootloader would usually do
|
|
// Since on other devices this runs every time, just do it here every time
|
|
std::ofstream final_rd(initrd_path,
|
|
std::ios_base::binary | std::ios_base::trunc);
|
|
|
|
std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
|
|
std::ios_base::binary);
|
|
std::ifstream vendor_boot_ramdisk(unpack_dir +
|
|
"/concatenated_vendor_ramdisk",
|
|
std::ios_base::binary);
|
|
|
|
std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
|
|
std::ios_base::binary |
|
|
std::ios_base::ate);
|
|
|
|
auto vb_size = vendor_boot_bootconfig.tellg();
|
|
vendor_boot_bootconfig.seekg(0);
|
|
|
|
std::ifstream persistent_bootconfig(bootconfig_path,
|
|
std::ios_base::binary |
|
|
std::ios_base::ate);
|
|
|
|
auto pb_size = persistent_bootconfig.tellg();
|
|
persistent_bootconfig.seekg(0);
|
|
|
|
// Build the bootconfig string, trim it, and write the length, checksum
|
|
// and trailer bytes
|
|
|
|
std::string bootconfig =
|
|
"androidboot.slot_suffix=_a\n"
|
|
"androidboot.force_normal_boot=1\n"
|
|
"androidboot.verifiedbootstate=orange\n";
|
|
auto bootconfig_size = bootconfig.size();
|
|
bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
|
|
vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
|
|
persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
|
|
// Trim the block size padding from the persistent bootconfig
|
|
bootconfig.erase(bootconfig.find_last_not_of('\0'));
|
|
|
|
// Write out the ramdisks and bootconfig blocks
|
|
final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
|
|
<< bootconfig;
|
|
|
|
// Append bootconfig length
|
|
bootconfig_size = bootconfig.size();
|
|
final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
|
|
sizeof(uint32_t));
|
|
|
|
// Append bootconfig checksum
|
|
uint32_t bootconfig_csum = 0;
|
|
for (auto i = 0; i < bootconfig_size; i++) {
|
|
bootconfig_csum += bootconfig[i];
|
|
}
|
|
final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
|
|
sizeof(uint32_t));
|
|
|
|
// Append bootconfig trailer
|
|
final_rd << "#BOOTCONFIG\n";
|
|
final_rd.close();
|
|
}
|
|
} // namespace cuttlefish
|