567 lines
20 KiB
C++
567 lines
20 KiB
C++
#include "host/libs/config/data_image.h"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/result.h>
|
|
|
|
#include "blkid.h"
|
|
|
|
#include "common/libs/fs/shared_buf.h"
|
|
#include "common/libs/utils/files.h"
|
|
#include "common/libs/utils/result.h"
|
|
#include "common/libs/utils/subprocess.h"
|
|
#include "host/libs/config/mbr.h"
|
|
#include "host/libs/vm_manager/gem5_manager.h"
|
|
|
|
namespace cuttlefish {
|
|
|
|
namespace {
|
|
const std::string kDataPolicyUseExisting = "use_existing";
|
|
const std::string kDataPolicyCreateIfMissing = "create_if_missing";
|
|
const std::string kDataPolicyAlwaysCreate = "always_create";
|
|
const std::string kDataPolicyResizeUpTo= "resize_up_to";
|
|
|
|
const int FSCK_ERROR_CORRECTED = 1;
|
|
const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
|
|
|
|
// Currently the Cuttlefish bootloaders are built only for x86 (32-bit),
|
|
// ARM (QEMU only, 32-bit) and AArch64 (64-bit), and U-Boot will hard-code
|
|
// these search paths. Install all bootloaders to one of these paths.
|
|
// NOTE: For now, just ignore the 32-bit ARM version, as Debian doesn't
|
|
// build an EFI monolith for this architecture.
|
|
const std::string kBootPathIA32 = "EFI/BOOT/BOOTIA32.EFI";
|
|
const std::string kBootPathAA64 = "EFI/BOOT/BOOTAA64.EFI";
|
|
const std::string kM5 = "";
|
|
|
|
// These are the paths Debian installs the monoliths to. If another distro
|
|
// uses an alternative monolith path, add it to this table
|
|
const std::pair<std::string, std::string> kGrubBlobTable[] = {
|
|
{"/usr/lib/grub/i386-efi/monolithic/grubia32.efi", kBootPathIA32},
|
|
{"/usr/lib/grub/arm64-efi/monolithic/grubaa64.efi", kBootPathAA64},
|
|
};
|
|
|
|
// M5 checkpoint required binary file
|
|
const std::pair<std::string, std::string> kM5BlobTable[] = {
|
|
{"/tmp/m5", kM5},
|
|
};
|
|
|
|
bool ForceFsckImage(const CuttlefishConfig& config,
|
|
const std::string& data_image) {
|
|
std::string fsck_path;
|
|
if (config.userdata_format() == "f2fs") {
|
|
fsck_path = HostBinaryPath("fsck.f2fs");
|
|
} else if (config.userdata_format() == "ext4") {
|
|
fsck_path = "/sbin/e2fsck";
|
|
}
|
|
int fsck_status = execute({fsck_path, "-y", "-f", data_image});
|
|
if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
|
|
LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code "
|
|
<< fsck_status;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NewfsMsdos(const std::string& data_image, int data_image_mb,
|
|
int offset_num_mb) {
|
|
off_t image_size_bytes = static_cast<off_t>(data_image_mb) << 20;
|
|
off_t offset_size_bytes = static_cast<off_t>(offset_num_mb) << 20;
|
|
image_size_bytes -= offset_size_bytes;
|
|
off_t image_size_sectors = image_size_bytes / 512;
|
|
auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
|
|
return execute({newfs_msdos_path,
|
|
"-F",
|
|
"32",
|
|
"-m",
|
|
"0xf8",
|
|
"-o",
|
|
"0",
|
|
"-c",
|
|
"8",
|
|
"-h",
|
|
"255",
|
|
"-u",
|
|
"63",
|
|
"-S",
|
|
"512",
|
|
"-s",
|
|
std::to_string(image_size_sectors),
|
|
"-C",
|
|
std::to_string(data_image_mb) + "M",
|
|
"-@",
|
|
std::to_string(offset_size_bytes),
|
|
data_image}) == 0;
|
|
}
|
|
|
|
bool ResizeImage(const CuttlefishConfig& config, const std::string& data_image,
|
|
int data_image_mb) {
|
|
auto file_mb = FileSize(data_image) >> 20;
|
|
if (file_mb > data_image_mb) {
|
|
LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
|
|
<< "resize down.";
|
|
return false;
|
|
} else if (file_mb == data_image_mb) {
|
|
LOG(INFO) << data_image << " is already the right size";
|
|
return true;
|
|
} else {
|
|
off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
|
|
auto fd = SharedFD::Open(data_image, O_RDWR);
|
|
if (fd->Truncate(raw_target) != 0) {
|
|
LOG(ERROR) << "`truncate --size=" << data_image_mb << "M "
|
|
<< data_image << "` failed:" << fd->StrError();
|
|
return false;
|
|
}
|
|
bool fsck_success = ForceFsckImage(config, data_image);
|
|
if (!fsck_success) {
|
|
return false;
|
|
}
|
|
std::string resize_path;
|
|
if (config.userdata_format() == "f2fs") {
|
|
resize_path = HostBinaryPath("resize.f2fs");
|
|
} else if (config.userdata_format() == "ext4") {
|
|
resize_path = "/sbin/resize2fs";
|
|
}
|
|
int resize_status = execute({resize_path, data_image});
|
|
if (resize_status != 0) {
|
|
LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code "
|
|
<< resize_status;
|
|
return false;
|
|
}
|
|
fsck_success = ForceFsckImage(config, data_image);
|
|
if (!fsck_success) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
bool CreateBlankImage(
|
|
const std::string& image, int num_mb, const std::string& image_fmt) {
|
|
LOG(DEBUG) << "Creating " << image;
|
|
|
|
off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
|
|
// The newfs_msdos tool with the mandatory -C option will do the same
|
|
// as below to zero the image file, so we don't need to do it here
|
|
if (image_fmt != "sdcard") {
|
|
auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
|
|
if (fd->Truncate(image_size_bytes) != 0) {
|
|
LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
|
|
<< "` failed:" << fd->StrError();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (image_fmt == "ext4") {
|
|
if (execute({"/sbin/mkfs.ext4", image}) != 0) {
|
|
return false;
|
|
}
|
|
} else if (image_fmt == "f2fs") {
|
|
auto make_f2fs_path = cuttlefish::HostBinaryPath("make_f2fs");
|
|
if (execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
|
|
"compression,extra_attr,project_quota", "-g", "android"}) != 0) {
|
|
return false;
|
|
}
|
|
} else if (image_fmt == "sdcard") {
|
|
// Reserve 1MB in the image for the MBR and padding, to simulate what
|
|
// other OSes do by default when partitioning a drive
|
|
off_t offset_size_bytes = 1 << 20;
|
|
image_size_bytes -= offset_size_bytes;
|
|
if (!NewfsMsdos(image, num_mb, 1)) {
|
|
LOG(ERROR) << "Failed to create SD-Card filesystem";
|
|
return false;
|
|
}
|
|
// Write the MBR after the filesystem is formatted, as the formatting tools
|
|
// don't consistently preserve the image contents
|
|
MasterBootRecord mbr = {
|
|
.partitions = {{
|
|
.partition_type = 0xC,
|
|
.first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
|
|
.num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
|
|
}},
|
|
.boot_signature = {0x55, 0xAA},
|
|
};
|
|
auto fd = SharedFD::Open(image, O_RDWR);
|
|
if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
|
|
LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
|
|
return false;
|
|
}
|
|
} else if (image_fmt != "none") {
|
|
LOG(WARNING) << "Unknown image format '" << image_fmt
|
|
<< "' for " << image << ", treating as 'none'.";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string GetFsType(const std::string& path) {
|
|
std::string fs_type;
|
|
blkid_cache cache;
|
|
if (blkid_get_cache(&cache, NULL) < 0) {
|
|
LOG(INFO) << "blkid_get_cache failed";
|
|
return fs_type;
|
|
}
|
|
blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
|
|
if (!dev) {
|
|
LOG(INFO) << "blkid_get_dev failed";
|
|
blkid_put_cache(cache);
|
|
return fs_type;
|
|
}
|
|
|
|
const char *type, *value;
|
|
blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
|
|
while (blkid_tag_next(iter, &type, &value) == 0) {
|
|
if (!strcmp(type, "TYPE")) {
|
|
fs_type = value;
|
|
}
|
|
}
|
|
blkid_tag_iterate_end(iter);
|
|
blkid_put_cache(cache);
|
|
return fs_type;
|
|
}
|
|
|
|
struct DataImageTag {};
|
|
|
|
class FixedDataImagePath : public DataImagePath {
|
|
public:
|
|
INJECT(FixedDataImagePath(ANNOTATED(DataImageTag, std::string) path))
|
|
: path_(path) {}
|
|
|
|
const std::string& Path() const override { return path_; }
|
|
|
|
private:
|
|
std::string path_;
|
|
};
|
|
|
|
fruit::Component<DataImagePath> FixedDataImagePathComponent(
|
|
const std::string* path) {
|
|
return fruit::createComponent()
|
|
.bind<DataImagePath, FixedDataImagePath>()
|
|
.bindInstance<fruit::Annotated<DataImageTag, std::string>>(*path);
|
|
}
|
|
|
|
class InitializeDataImageImpl : public InitializeDataImage {
|
|
public:
|
|
INJECT(InitializeDataImageImpl(const CuttlefishConfig& config,
|
|
DataImagePath& data_path))
|
|
: config_(config), data_path_(data_path) {}
|
|
|
|
// SetupFeature
|
|
std::string Name() const override { return "InitializeDataImageImpl"; }
|
|
bool Enabled() const override { return true; }
|
|
|
|
private:
|
|
std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
|
|
bool Setup() override {
|
|
auto action = ChooseAction();
|
|
if (!action.ok()) {
|
|
LOG(ERROR) << "Failed to select a userdata processing action: "
|
|
<< action.error();
|
|
return false;
|
|
}
|
|
auto result = EvaluateAction(*action);
|
|
if (!result.ok()) {
|
|
LOG(ERROR) << "Failed to evaluate userdata action: " << result.error();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
|
|
|
|
Result<DataImageAction> ChooseAction() {
|
|
if (config_.data_policy() == kDataPolicyAlwaysCreate) {
|
|
return DataImageAction::kCreateImage;
|
|
}
|
|
if (!FileHasContent(data_path_.Path())) {
|
|
if (config_.data_policy() == kDataPolicyUseExisting) {
|
|
return CF_ERR("A data image must exist to use -data_policy="
|
|
<< kDataPolicyUseExisting);
|
|
} else if (config_.data_policy() == kDataPolicyResizeUpTo) {
|
|
return CF_ERR(data_path_.Path()
|
|
<< " does not exist, but resizing was requested");
|
|
}
|
|
return DataImageAction::kCreateImage;
|
|
}
|
|
if (GetFsType(data_path_.Path()) != config_.userdata_format()) {
|
|
CF_EXPECT(config_.data_policy() == kDataPolicyResizeUpTo,
|
|
"Changing the fs format is incompatible with -data_policy="
|
|
<< kDataPolicyResizeUpTo);
|
|
return DataImageAction::kCreateImage;
|
|
}
|
|
if (config_.data_policy() == kDataPolicyResizeUpTo) {
|
|
return DataImageAction::kResizeImage;
|
|
}
|
|
return DataImageAction::kNoAction;
|
|
}
|
|
|
|
Result<void> EvaluateAction(DataImageAction action) {
|
|
switch (action) {
|
|
case DataImageAction::kNoAction:
|
|
LOG(DEBUG) << data_path_.Path() << " exists. Not creating it.";
|
|
return {};
|
|
case DataImageAction::kCreateImage: {
|
|
RemoveFile(data_path_.Path());
|
|
CF_EXPECT(config_.blank_data_image_mb() != 0,
|
|
"Expected `-blank_data_image_mb` to be set for "
|
|
"image creation.");
|
|
CF_EXPECT(
|
|
CreateBlankImage(data_path_.Path(), config_.blank_data_image_mb(),
|
|
config_.userdata_format()),
|
|
"Failed to create a blank image at \""
|
|
<< data_path_.Path() << "\" with size "
|
|
<< config_.blank_data_image_mb() << " and format \""
|
|
<< config_.userdata_format() << "\"");
|
|
return {};
|
|
}
|
|
case DataImageAction::kResizeImage: {
|
|
CF_EXPECT(config_.blank_data_image_mb() != 0,
|
|
"Expected `-blank_data_image_mb` to be set for "
|
|
"image resizing.");
|
|
CF_EXPECT(ResizeImage(config_, data_path_.Path(),
|
|
config_.blank_data_image_mb()),
|
|
"Failed to resize \"" << data_path_.Path() << "\" to "
|
|
<< config_.blank_data_image_mb()
|
|
<< " MB");
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
const CuttlefishConfig& config_;
|
|
DataImagePath& data_path_;
|
|
};
|
|
|
|
fruit::Component<fruit::Required<const CuttlefishConfig, DataImagePath>,
|
|
InitializeDataImage>
|
|
InitializeDataImageComponent() {
|
|
return fruit::createComponent()
|
|
.addMultibinding<SetupFeature, InitializeDataImage>()
|
|
.bind<InitializeDataImage, InitializeDataImageImpl>();
|
|
}
|
|
|
|
struct MiscImageTag {};
|
|
|
|
class FixedMiscImagePath : public MiscImagePath {
|
|
public:
|
|
INJECT(FixedMiscImagePath(ANNOTATED(MiscImageTag, std::string) path))
|
|
: path_(path) {}
|
|
|
|
const std::string& Path() const override { return path_; }
|
|
|
|
private:
|
|
std::string path_;
|
|
};
|
|
|
|
class InitializeMiscImageImpl : public InitializeMiscImage {
|
|
public:
|
|
INJECT(InitializeMiscImageImpl(MiscImagePath& misc_path))
|
|
: misc_path_(misc_path) {}
|
|
|
|
// SetupFeature
|
|
std::string Name() const override { return "InitializeMiscImageImpl"; }
|
|
bool Enabled() const override { return true; }
|
|
|
|
private:
|
|
std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
|
|
bool Setup() override {
|
|
bool misc_exists = FileHasContent(misc_path_.Path());
|
|
|
|
if (misc_exists) {
|
|
LOG(DEBUG) << "misc partition image: use existing at \""
|
|
<< misc_path_.Path() << "\"";
|
|
return true;
|
|
}
|
|
|
|
LOG(DEBUG) << "misc partition image: creating empty at \""
|
|
<< misc_path_.Path() << "\"";
|
|
if (!CreateBlankImage(misc_path_.Path(), 1 /* mb */, "none")) {
|
|
LOG(ERROR) << "Failed to create misc image";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
MiscImagePath& misc_path_;
|
|
};
|
|
|
|
fruit::Component<MiscImagePath> FixedMiscImagePathComponent(
|
|
const std::string* path) {
|
|
return fruit::createComponent()
|
|
.bind<MiscImagePath, FixedMiscImagePath>()
|
|
.bindInstance<fruit::Annotated<MiscImageTag, std::string>>(*path);
|
|
}
|
|
|
|
fruit::Component<fruit::Required<MiscImagePath>, InitializeMiscImage>
|
|
InitializeMiscImageComponent() {
|
|
return fruit::createComponent()
|
|
.addMultibinding<SetupFeature, InitializeMiscImage>()
|
|
.bind<InitializeMiscImage, InitializeMiscImageImpl>();
|
|
}
|
|
|
|
struct EspImageTag {};
|
|
struct KernelPathTag {};
|
|
struct InitRamFsTag {};
|
|
struct RootFsTag {};
|
|
struct ConfigTag {};
|
|
|
|
class InitializeEspImageImpl : public InitializeEspImage {
|
|
public:
|
|
INJECT(InitializeEspImageImpl(ANNOTATED(EspImageTag, std::string) esp_image,
|
|
ANNOTATED(KernelPathTag, std::string)
|
|
kernel_path,
|
|
ANNOTATED(InitRamFsTag, std::string)
|
|
initramfs_path,
|
|
ANNOTATED(RootFsTag, std::string) rootfs_path,
|
|
ANNOTATED(ConfigTag, const CuttlefishConfig *) config))
|
|
: esp_image_(esp_image),
|
|
kernel_path_(kernel_path),
|
|
initramfs_path_(initramfs_path),
|
|
rootfs_path_(rootfs_path),
|
|
config_(config){}
|
|
|
|
// SetupFeature
|
|
std::string Name() const override { return "InitializeEspImageImpl"; }
|
|
std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
|
|
bool Enabled() const override { return !rootfs_path_.empty(); }
|
|
|
|
protected:
|
|
bool Setup() override {
|
|
bool esp_exists = FileHasContent(esp_image_);
|
|
if (esp_exists) {
|
|
LOG(DEBUG) << "esp partition image: use existing";
|
|
return true;
|
|
}
|
|
|
|
LOG(DEBUG) << "esp partition image: creating default";
|
|
|
|
// newfs_msdos won't make a partition smaller than 257 mb
|
|
// this should be enough for anybody..
|
|
auto tmp_esp_image = esp_image_ + ".tmp";
|
|
if (!NewfsMsdos(tmp_esp_image, 257 /* mb */, 0 /* mb (offset) */)) {
|
|
LOG(ERROR) << "Failed to create filesystem for " << tmp_esp_image;
|
|
return false;
|
|
}
|
|
|
|
// For licensing and build reproducibility reasons, pick up the bootloaders
|
|
// from the host Linux distribution (if present) and pack them into the
|
|
// automatically generated ESP. If the user wants their own bootloaders,
|
|
// they can use -esp_image=/path/to/esp.img to override, so we don't need
|
|
// to accommodate customizations of this packing process.
|
|
|
|
int success;
|
|
const std::pair<std::string, std::string> *kBlobTable;
|
|
std::size_t size;
|
|
// Skip GRUB on Gem5
|
|
if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
|
|
// Currently we only support Debian based distributions, and GRUB is built
|
|
// for those distros to always load grub.cfg from EFI/debian/grub.cfg, and
|
|
// nowhere else. If you want to add support for other distros, make the
|
|
// extra directories below and copy the initial grub.cfg there as well
|
|
auto mmd = HostBinaryPath("mmd");
|
|
success =
|
|
execute({mmd, "-i", tmp_esp_image, "EFI", "EFI/BOOT", "EFI/debian"});
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Failed to create directories in " << tmp_esp_image;
|
|
return false;
|
|
}
|
|
size = sizeof(kGrubBlobTable)/sizeof(const std::pair<std::string, std::string>);
|
|
kBlobTable = kGrubBlobTable;
|
|
} else {
|
|
size = sizeof(kM5BlobTable)/sizeof(const std::pair<std::string, std::string>);
|
|
kBlobTable = kM5BlobTable;
|
|
}
|
|
|
|
// The grub binaries are small, so just copy all the architecture blobs
|
|
// we can find, which minimizes complexity. If the user removed the grub bin
|
|
// package from their system, the ESP will be empty and Other OS will not be
|
|
// supported
|
|
auto mcopy = HostBinaryPath("mcopy");
|
|
bool copied = false;
|
|
for (int i=0; i<size; i++) {
|
|
auto grub = kBlobTable[i];
|
|
if (!FileExists(grub.first)) {
|
|
continue;
|
|
}
|
|
success = execute({mcopy, "-o", "-i", tmp_esp_image, "-s", grub.first,
|
|
"::" + grub.second});
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Failed to copy " << grub.first << " to " << grub.second
|
|
<< " in " << tmp_esp_image;
|
|
return false;
|
|
}
|
|
copied = true;
|
|
}
|
|
|
|
if (!copied) {
|
|
LOG(ERROR) << "Binary dependencies were not found on this system; Other OS "
|
|
"support will be broken";
|
|
return false;
|
|
}
|
|
|
|
// Skip Gem5 case. Gem5 will never be able to use bootloaders like grub.
|
|
if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
|
|
auto grub_cfg = DefaultHostArtifactsPath("etc/grub/grub.cfg");
|
|
CHECK(FileExists(grub_cfg)) << "Missing file " << grub_cfg << "!";
|
|
success =
|
|
execute({mcopy, "-i", tmp_esp_image, "-s", grub_cfg, "::EFI/debian/"});
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Failed to copy " << grub_cfg << " to " << tmp_esp_image;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!kernel_path_.empty()) {
|
|
success = execute(
|
|
{mcopy, "-i", tmp_esp_image, "-s", kernel_path_, "::vmlinuz"});
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Failed to copy " << kernel_path_ << " to "
|
|
<< tmp_esp_image;
|
|
return false;
|
|
}
|
|
|
|
if (!initramfs_path_.empty()) {
|
|
success = execute({mcopy, "-i", tmp_esp_image, "-s", initramfs_path_,
|
|
"::initrd.img"});
|
|
if (success != 0) {
|
|
LOG(ERROR) << "Failed to copy " << initramfs_path_ << " to "
|
|
<< tmp_esp_image;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cuttlefish::RenameFile(tmp_esp_image, esp_image_)) {
|
|
LOG(ERROR) << "Renaming " << tmp_esp_image << " to " << esp_image_
|
|
<< " failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
std::string esp_image_;
|
|
std::string kernel_path_;
|
|
std::string initramfs_path_;
|
|
std::string rootfs_path_;
|
|
const CuttlefishConfig* config_;
|
|
};
|
|
|
|
fruit::Component<fruit::Required<const CuttlefishConfig>,
|
|
InitializeEspImage> InitializeEspImageComponent(
|
|
const std::string* esp_image, const std::string* kernel_path,
|
|
const std::string* initramfs_path, const std::string* rootfs_path,
|
|
const CuttlefishConfig* config) {
|
|
return fruit::createComponent()
|
|
.addMultibinding<SetupFeature, InitializeEspImage>()
|
|
.bind<InitializeEspImage, InitializeEspImageImpl>()
|
|
.bindInstance<fruit::Annotated<EspImageTag, std::string>>(*esp_image)
|
|
.bindInstance<fruit::Annotated<KernelPathTag, std::string>>(*kernel_path)
|
|
.bindInstance<fruit::Annotated<InitRamFsTag, std::string>>(
|
|
*initramfs_path)
|
|
.bindInstance<fruit::Annotated<RootFsTag, std::string>>(*rootfs_path)
|
|
.bindInstance<fruit::Annotated<ConfigTag, CuttlefishConfig>>(*config);
|
|
}
|
|
|
|
} // namespace cuttlefish
|