1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/commands/assemble_cvd/disk/disk.h"
18
19 #include <string>
20
21 #include <fruit/fruit.h>
22 #include <gflags/gflags.h>
23
24 #include "common/libs/utils/files.h"
25 #include "host/commands/assemble_cvd/boot_image_utils.h"
26 #include "host/commands/assemble_cvd/vendor_dlkm_utils.h"
27 #include "host/libs/avb/avb.h"
28 #include "host/libs/config/cuttlefish_config.h"
29 #include "host/libs/config/feature.h"
30 #include "host/libs/vm_manager/gem5_manager.h"
31
32 namespace cuttlefish {
33
34 using vm_manager::Gem5Manager;
35
36 class KernelRamdiskRepackerImpl : public KernelRamdiskRepacker {
37 public:
INJECT(KernelRamdiskRepackerImpl (const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance,const Avb & avb))38 INJECT(KernelRamdiskRepackerImpl(
39 const CuttlefishConfig& config,
40 const CuttlefishConfig::InstanceSpecific& instance,
41 const Avb& avb))
42 : config_(config), instance_(instance), avb_(avb) {}
43
44 // SetupFeature
Name() const45 std::string Name() const override { return "KernelRamdiskRepacker"; }
Dependencies() const46 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Enabled() const47 bool Enabled() const override {
48 // If we are booting a protected VM, for now, assume that image repacking
49 // isn't trusted. Repacking requires resigning the image and keys from an
50 // android host aren't trusted.
51 return !instance_.protected_vm();
52 }
53
54 protected:
RebuildDlkmAndVbmeta(const std::string & build_dir,const std::string & partition_name,const std::string & output_image,const std::string & vbmeta_image)55 static bool RebuildDlkmAndVbmeta(const std::string& build_dir,
56 const std::string& partition_name,
57 const std::string& output_image,
58 const std::string& vbmeta_image) {
59 // TODO(b/149866755) For now, we assume that vendor_dlkm is ext4. Add
60 // logic to handle EROFS once the feature stabilizes.
61 const auto tmp_output_image = output_image + ".tmp";
62 if (!BuildDlkmImage(build_dir, false, partition_name, tmp_output_image)) {
63 LOG(ERROR) << "Failed to build `" << partition_name << "` image from "
64 << build_dir;
65 return false;
66 }
67 if (!MoveIfChanged(tmp_output_image, output_image)) {
68 return false;
69 }
70 if (!BuildVbmetaImage(output_image, vbmeta_image)) {
71 LOG(ERROR) << "Failed to rebuild vbmeta vendor.";
72 return false;
73 }
74 return true;
75 }
RepackSuperAndVbmeta(const std::string & superimg_build_dir,const std::string & vendor_dlkm_build_dir,const std::string & system_dlkm_build_dir,const std::string & ramdisk_path)76 bool RepackSuperAndVbmeta(const std::string& superimg_build_dir,
77 const std::string& vendor_dlkm_build_dir,
78 const std::string& system_dlkm_build_dir,
79 const std::string& ramdisk_path) {
80 const auto ramdisk_stage_dir = instance_.instance_dir() + "/ramdisk_staged";
81 if (!SplitRamdiskModules(ramdisk_path, ramdisk_stage_dir,
82 vendor_dlkm_build_dir, system_dlkm_build_dir)) {
83 LOG(ERROR) << "Failed to move ramdisk modules to vendor_dlkm";
84 return false;
85 }
86 const auto new_vendor_dlkm_img =
87 superimg_build_dir + "/vendor_dlkm_repacked.img";
88 if (!RebuildDlkmAndVbmeta(vendor_dlkm_build_dir, "vendor_dlkm",
89 new_vendor_dlkm_img,
90 instance_.new_vbmeta_vendor_dlkm_image())) {
91 LOG(ERROR) << "Failed to build vendor_dlkm image from "
92 << vendor_dlkm_build_dir;
93 return false;
94 }
95 const auto new_system_dlkm_img =
96 superimg_build_dir + "/system_dlkm_repacked.img";
97 if (!RebuildDlkmAndVbmeta(system_dlkm_build_dir, "system_dlkm",
98 new_system_dlkm_img,
99 instance_.new_vbmeta_system_dlkm_image())) {
100 LOG(ERROR) << "Failed to build system_dlkm image from "
101 << system_dlkm_build_dir;
102 return false;
103 }
104 const auto new_super_img = instance_.new_super_image();
105 if (!Copy(instance_.super_image(), new_super_img)) {
106 PLOG(ERROR) << "Failed to copy super image " << instance_.super_image()
107 << " to " << new_super_img;
108 return false;
109 }
110 if (!RepackSuperWithPartition(new_super_img, new_vendor_dlkm_img,
111 "vendor_dlkm")) {
112 LOG(ERROR) << "Failed to repack super image with new vendor dlkm image.";
113 return false;
114 }
115 if (!RepackSuperWithPartition(new_super_img, new_system_dlkm_img,
116 "system_dlkm")) {
117 LOG(ERROR) << "Failed to repack super image with new system dlkm image.";
118 return false;
119 }
120 return true;
121 }
ResultSetup()122 Result<void> ResultSetup() override {
123 CF_EXPECTF(FileHasContent(instance_.boot_image()), "File not found: {}",
124 instance_.boot_image());
125 // The init_boot partition is be optional for testing boot.img
126 // with the ramdisk inside.
127 if (!FileHasContent(instance_.init_boot_image())) {
128 LOG(WARNING) << "File not found: " << instance_.init_boot_image();
129 }
130
131 CF_EXPECTF(FileHasContent(instance_.vendor_boot_image()),
132 "File not found: {}", instance_.vendor_boot_image());
133
134 // Repacking a boot.img doesn't work with Gem5 because the user must always
135 // specify a vmlinux instead of an arm64 Image, and that file can be too
136 // large to be repacked. Skip repack of boot.img on Gem5, as we need to be
137 // able to extract the ramdisk.img in a later stage and so this step must
138 // not fail (..and the repacked kernel wouldn't be used anyway).
139 if (instance_.kernel_path().size() &&
140 config_.vm_manager() != VmmMode::kGem5) {
141 CF_EXPECT(RepackBootImage(avb_, instance_.kernel_path(), instance_.boot_image(),
142 instance_.new_boot_image(), instance_.instance_dir()),
143 "Failed to regenerate the boot image with the new kernel");
144 }
145
146 if (instance_.kernel_path().size() || instance_.initramfs_path().size()) {
147 const std::string new_vendor_boot_image_path =
148 instance_.new_vendor_boot_image();
149 // Repack the vendor boot images if kernels and/or ramdisks are passed in.
150 if (instance_.initramfs_path().size()) {
151 const auto superimg_build_dir = instance_.instance_dir() + "/superimg";
152 const auto ramdisk_repacked =
153 instance_.instance_dir() + "/ramdisk_repacked";
154 CF_EXPECTF(Copy(instance_.initramfs_path(), ramdisk_repacked),
155 "Failed to copy {} to {}", instance_.initramfs_path(),
156 ramdisk_repacked);
157 const auto vendor_dlkm_build_dir = superimg_build_dir + "/vendor_dlkm";
158 const auto system_dlkm_build_dir = superimg_build_dir + "/system_dlkm";
159 CF_EXPECT(
160 RepackSuperAndVbmeta(superimg_build_dir, vendor_dlkm_build_dir,
161 system_dlkm_build_dir, ramdisk_repacked));
162 bool success = RepackVendorBootImage(
163 ramdisk_repacked, instance_.vendor_boot_image(),
164 new_vendor_boot_image_path, config_.assembly_dir(),
165 instance_.bootconfig_supported());
166 if (!success) {
167 LOG(ERROR) << "Failed to regenerate the vendor boot image with the "
168 "new ramdisk";
169 } else {
170 // This control flow implies a kernel with all configs built in.
171 // If it's just the kernel, repack the vendor boot image without a
172 // ramdisk.
173 CF_EXPECT(
174 RepackVendorBootImageWithEmptyRamdisk(
175 instance_.vendor_boot_image(), new_vendor_boot_image_path,
176 config_.assembly_dir(), instance_.bootconfig_supported()),
177 "Failed to regenerate the vendor boot image without a ramdisk");
178 }
179 }
180 }
181 return {};
182 }
183
184 private:
185 const CuttlefishConfig& config_;
186 const CuttlefishConfig::InstanceSpecific& instance_;
187 const Avb& avb_;
188 };
189
190 fruit::Component<fruit::Required<const CuttlefishConfig,
191 const CuttlefishConfig::InstanceSpecific,
192 const Avb>,
193 KernelRamdiskRepacker>
KernelRamdiskRepackerComponent()194 KernelRamdiskRepackerComponent() {
195 return fruit::createComponent()
196 .addMultibinding<SetupFeature, KernelRamdiskRepackerImpl>()
197 .bind<KernelRamdiskRepacker, KernelRamdiskRepackerImpl>();
198 }
199
200 } // namespace cuttlefish
201