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