blob: ec9e93f013c52724f345660759b56693b04d5058 [file] [log] [blame]
/*
* 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