1 /*
2  * Copyright (C) 2019 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 #include "host/libs/config/data_image.h"
17 
18 #include <android-base/logging.h>
19 #include <android-base/result.h>
20 
21 #include "blkid.h"
22 
23 #include "common/libs/fs/shared_buf.h"
24 #include "common/libs/utils/files.h"
25 #include "common/libs/utils/result.h"
26 #include "common/libs/utils/subprocess.h"
27 #include "host/libs/config/cuttlefish_config.h"
28 #include "host/libs/config/esp.h"
29 #include "host/libs/config/mbr.h"
30 #include "host/libs/config/openwrt_args.h"
31 #include "host/libs/vm_manager/gem5_manager.h"
32 
33 namespace cuttlefish {
34 
35 namespace {
36 
37 static constexpr std::string_view kDataPolicyUseExisting = "use_existing";
38 static constexpr std::string_view kDataPolicyAlwaysCreate = "always_create";
39 static constexpr std::string_view kDataPolicyResizeUpTo = "resize_up_to";
40 
41 const int FSCK_ERROR_CORRECTED = 1;
42 const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
43 
ForceFsckImage(const std::string & data_image,const CuttlefishConfig::InstanceSpecific & instance)44 Result<void> ForceFsckImage(
45     const std::string& data_image,
46     const CuttlefishConfig::InstanceSpecific& instance) {
47   std::string fsck_path;
48   if (instance.userdata_format() == "f2fs") {
49     fsck_path = HostBinaryPath("fsck.f2fs");
50   } else if (instance.userdata_format() == "ext4") {
51     fsck_path = "/sbin/e2fsck";
52   }
53   int fsck_status = Execute({fsck_path, "-y", "-f", data_image});
54   CF_EXPECTF(!(fsck_status &
55                ~(FSCK_ERROR_CORRECTED | FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)),
56              "`{} -y -f {}` failed with code {}", fsck_path, data_image,
57              fsck_status);
58   return {};
59 }
60 
ResizeImage(const std::string & data_image,int data_image_mb,const CuttlefishConfig::InstanceSpecific & instance)61 Result<void> ResizeImage(const std::string& data_image, int data_image_mb,
62                          const CuttlefishConfig::InstanceSpecific& instance) {
63   auto file_mb = FileSize(data_image) >> 20;
64   CF_EXPECTF(data_image_mb <= file_mb, "'{}' is already {} MB, won't downsize",
65              data_image, file_mb);
66   if (file_mb == data_image_mb) {
67     LOG(INFO) << data_image << " is already the right size";
68     return {};
69   }
70   off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
71   auto fd = SharedFD::Open(data_image, O_RDWR);
72   CF_EXPECTF(fd->IsOpen(), "Can't open '{}': '{}'", data_image, fd->StrError());
73   CF_EXPECTF(fd->Truncate(raw_target) == 0, "`truncate --size={}M {} fail: {}",
74              data_image_mb, data_image, fd->StrError());
75   CF_EXPECT(ForceFsckImage(data_image, instance));
76   std::string resize_path;
77   if (instance.userdata_format() == "f2fs") {
78     resize_path = HostBinaryPath("resize.f2fs");
79   } else if (instance.userdata_format() == "ext4") {
80     resize_path = "/sbin/resize2fs";
81   }
82   if (resize_path != "") {
83     CF_EXPECT_EQ(Execute({resize_path, data_image}), 0,
84                  "`" << resize_path << " " << data_image << "` failed");
85     CF_EXPECT(ForceFsckImage(data_image, instance));
86   }
87 
88   return {};
89 }
90 
GetFsType(const std::string & path)91 std::string GetFsType(const std::string& path) {
92   std::string fs_type;
93   blkid_cache cache;
94   if (blkid_get_cache(&cache, NULL) < 0) {
95     LOG(INFO) << "blkid_get_cache failed";
96     return fs_type;
97   }
98   blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
99   if (!dev) {
100     LOG(INFO) << "blkid_get_dev failed";
101     blkid_put_cache(cache);
102     return fs_type;
103   }
104 
105   const char *type, *value;
106   blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
107   while (blkid_tag_next(iter, &type, &value) == 0) {
108     if (!strcmp(type, "TYPE")) {
109       fs_type = value;
110     }
111   }
112   blkid_tag_iterate_end(iter);
113   blkid_put_cache(cache);
114   return fs_type;
115 }
116 
117 enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
118 
ChooseDataImageAction(const CuttlefishConfig::InstanceSpecific & instance)119 static Result<DataImageAction> ChooseDataImageAction(
120     const CuttlefishConfig::InstanceSpecific& instance) {
121   if (instance.data_policy() == kDataPolicyAlwaysCreate) {
122     return DataImageAction::kCreateImage;
123   }
124   if (!FileHasContent(instance.data_image())) {
125     if (instance.data_policy() == kDataPolicyUseExisting) {
126       return CF_ERR("A data image must exist to use -data_policy="
127                     << kDataPolicyUseExisting);
128     } else if (instance.data_policy() == kDataPolicyResizeUpTo) {
129       return CF_ERR(instance.data_image()
130                     << " does not exist, but resizing was requested");
131     }
132     return DataImageAction::kCreateImage;
133   }
134   if (instance.data_policy() == kDataPolicyUseExisting) {
135     return DataImageAction::kNoAction;
136   }
137   auto current_fs_type = GetFsType(instance.data_image());
138   if (current_fs_type != instance.userdata_format()) {
139     CF_EXPECT(instance.data_policy() != kDataPolicyResizeUpTo,
140               "Changing the fs format is incompatible with -data_policy="
141                   << kDataPolicyResizeUpTo << " (\"" << current_fs_type
142                   << "\" != \"" << instance.userdata_format() << "\")");
143     return DataImageAction::kCreateImage;
144   }
145   if (instance.data_policy() == kDataPolicyResizeUpTo) {
146     return DataImageAction::kResizeImage;
147   }
148   return DataImageAction::kNoAction;
149 }
150 
151 } // namespace
152 
CreateBlankImage(const std::string & image,int num_mb,const std::string & image_fmt)153 Result<void> CreateBlankImage(const std::string& image, int num_mb,
154                               const std::string& image_fmt) {
155   LOG(DEBUG) << "Creating " << image;
156 
157   off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
158   // The newfs_msdos tool with the mandatory -C option will do the same
159   // as below to zero the image file, so we don't need to do it here
160   if (image_fmt != "sdcard") {
161     auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
162     CF_EXPECTF(fd->Truncate(image_size_bytes) == 0,
163                "`truncate --size={}M '{}'` failed: {}", num_mb, image,
164                fd->StrError());
165   }
166 
167   if (image_fmt == "ext4") {
168     CF_EXPECT(Execute({"/sbin/mkfs.ext4", image}) == 0);
169   } else if (image_fmt == "f2fs") {
170     auto make_f2fs_path = HostBinaryPath("make_f2fs");
171     CF_EXPECT(
172         Execute({make_f2fs_path, "-l", "data", image, "-C", "utf8", "-O",
173                  "compression,extra_attr,project_quota,casefold", "-g",
174                  "android", "-b", F2FS_BLOCKSIZE, "-w", F2FS_BLOCKSIZE}) == 0);
175   } else if (image_fmt == "sdcard") {
176     // Reserve 1MB in the image for the MBR and padding, to simulate what
177     // other OSes do by default when partitioning a drive
178     off_t offset_size_bytes = 1 << 20;
179     image_size_bytes -= offset_size_bytes;
180     CF_EXPECT(NewfsMsdos(image, num_mb, 1), "Failed to create SD-Card fs");
181     // Write the MBR after the filesystem is formatted, as the formatting tools
182     // don't consistently preserve the image contents
183     MasterBootRecord mbr = {
184         .partitions = {{
185             .partition_type = 0xC,
186             .first_lba = (std::uint32_t)offset_size_bytes / kSectorSize,
187             .num_sectors = (std::uint32_t)image_size_bytes / kSectorSize,
188         }},
189         .boot_signature = {0x55, 0xAA},
190     };
191     auto fd = SharedFD::Open(image, O_RDWR);
192     CF_EXPECTF(WriteAllBinary(fd, &mbr) == sizeof(MasterBootRecord),
193                "Writing MBR to '{}' failed: '{}'", image, fd->StrError());
194   } else if (image_fmt != "none") {
195     LOG(WARNING) << "Unknown image format '" << image_fmt
196                  << "' for " << image << ", treating as 'none'.";
197   }
198   return {};
199 }
200 
InitializeDataImage(const CuttlefishConfig::InstanceSpecific & instance)201 Result<void> InitializeDataImage(
202     const CuttlefishConfig::InstanceSpecific& instance) {
203   auto action = CF_EXPECT(ChooseDataImageAction(instance));
204   switch (action) {
205     case DataImageAction::kNoAction:
206       LOG(DEBUG) << instance.data_image() << " exists. Not creating it.";
207       return {};
208     case DataImageAction::kCreateImage: {
209       RemoveFile(instance.new_data_image());
210       CF_EXPECT(instance.blank_data_image_mb() != 0,
211                 "Expected `-blank_data_image_mb` to be set for "
212                 "image creation.");
213       CF_EXPECT(CreateBlankImage(instance.new_data_image(),
214                                  instance.blank_data_image_mb(),
215                                  instance.userdata_format()),
216                 "Failed to create a blank image at \""
217                     << instance.new_data_image() << "\" with size "
218                     << instance.blank_data_image_mb() << " and format \""
219                     << instance.userdata_format() << "\"");
220       return {};
221     }
222     case DataImageAction::kResizeImage: {
223       CF_EXPECT(instance.blank_data_image_mb() != 0,
224                 "Expected `-blank_data_image_mb` to be set for "
225                 "image resizing.");
226       CF_EXPECTF(Copy(instance.data_image(), instance.new_data_image()),
227                  "Failed to `cp {} {}`", instance.data_image(),
228                  instance.new_data_image());
229       CF_EXPECT(ResizeImage(instance.new_data_image(),
230                             instance.blank_data_image_mb(), instance),
231                 "Failed to resize \"" << instance.new_data_image() << "\" to "
232                                       << instance.blank_data_image_mb()
233                                       << " MB");
234       return {};
235     }
236   }
237 }
238 
InitializeMiscImage(const CuttlefishConfig::InstanceSpecific & instance)239 Result<void> InitializeMiscImage(
240     const CuttlefishConfig::InstanceSpecific& instance) {
241   if (FileHasContent(instance.misc_image())) {
242     LOG(DEBUG) << "misc partition image already exists";
243     return {};
244   }
245 
246   LOG(DEBUG) << "misc partition image: creating empty at \""
247              << instance.misc_image() << "\"";
248   CF_EXPECT(CreateBlankImage(instance.misc_image(), 1 /* mb */, "none"),
249             "Failed to create misc image");
250   return {};
251 }
252 
253 class InitializeEspImageImpl : public InitializeEspImage {
254  public:
INJECT(InitializeEspImageImpl (const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance))255   INJECT(InitializeEspImageImpl(
256       const CuttlefishConfig& config,
257       const CuttlefishConfig::InstanceSpecific& instance))
258       : config_(config), instance_(instance) {}
259 
260   // SetupFeature
Name() const261   std::string Name() const override { return "InitializeEspImageImpl"; }
Dependencies() const262   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
263 
Enabled() const264   bool Enabled() const override {
265     return EspRequiredForBootFlow() || EspRequiredForAPBootFlow();
266   }
267 
268  protected:
ResultSetup()269   Result<void> ResultSetup() override {
270     if (EspRequiredForAPBootFlow()) {
271       LOG(DEBUG) << "creating esp_image: " << instance_.ap_esp_image_path();
272       CF_EXPECT(BuildAPImage());
273     }
274     const auto is_not_gem5 = config_.vm_manager() != VmmMode::kGem5;
275     const auto esp_required_for_boot_flow = EspRequiredForBootFlow();
276     if (is_not_gem5 && esp_required_for_boot_flow) {
277       LOG(DEBUG) << "creating esp_image: " << instance_.esp_image_path();
278       CF_EXPECT(BuildOSImage());
279     }
280     return {};
281   }
282 
283  private:
284 
EspRequiredForBootFlow() const285   bool EspRequiredForBootFlow() const {
286     const auto flow = instance_.boot_flow();
287     using BootFlow = CuttlefishConfig::InstanceSpecific::BootFlow;
288     return flow == BootFlow::AndroidEfiLoader || flow == BootFlow::ChromeOs ||
289            flow == BootFlow::Linux || flow == BootFlow::Fuchsia;
290   }
291 
EspRequiredForAPBootFlow() const292   bool EspRequiredForAPBootFlow() const {
293     return instance_.ap_boot_flow() == CuttlefishConfig::InstanceSpecific::APBootFlow::Grub;
294   }
295 
BuildAPImage()296   bool BuildAPImage() {
297     auto linux = LinuxEspBuilder(instance_.ap_esp_image_path());
298     InitLinuxArgs(linux);
299 
300     auto openwrt_args = OpenwrtArgsFromConfig(instance_);
301     for (auto& openwrt_arg : openwrt_args) {
302       linux.Argument(openwrt_arg.first, openwrt_arg.second);
303     }
304 
305     linux.Root("/dev/vda2")
306          .Architecture(instance_.target_arch())
307          .Kernel(config_.ap_kernel_image());
308 
309     return linux.Build();
310   }
311 
BuildOSImage()312   bool BuildOSImage() {
313     switch (instance_.boot_flow()) {
314       case CuttlefishConfig::InstanceSpecific::BootFlow::AndroidEfiLoader: {
315         auto android_efi_loader =
316             AndroidEfiLoaderEspBuilder(instance_.esp_image_path());
317         android_efi_loader.EfiLoaderPath(instance_.android_efi_loader())
318             .Architecture(instance_.target_arch());
319         return android_efi_loader.Build();
320       }
321       case CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOs: {
322         auto linux = LinuxEspBuilder(instance_.esp_image_path());
323         InitChromeOsArgs(linux);
324 
325         linux.Root("/dev/vda3")
326             .Architecture(instance_.target_arch())
327             .Kernel(instance_.chromeos_kernel_path());
328 
329         return linux.Build();
330       }
331       case CuttlefishConfig::InstanceSpecific::BootFlow::Linux: {
332         auto linux = LinuxEspBuilder(instance_.esp_image_path());
333         InitLinuxArgs(linux);
334 
335         linux.Root("/dev/vda2")
336              .Architecture(instance_.target_arch())
337              .Kernel(instance_.linux_kernel_path());
338 
339         if (!instance_.linux_initramfs_path().empty()) {
340           linux.Initrd(instance_.linux_initramfs_path());
341         }
342 
343         return linux.Build();
344       }
345       case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia: {
346         auto fuchsia = FuchsiaEspBuilder(instance_.esp_image_path());
347         return fuchsia.Architecture(instance_.target_arch())
348                       .Zedboot(instance_.fuchsia_zedboot_path())
349                       .MultibootBinary(instance_.fuchsia_multiboot_bin_path())
350                       .Build();
351       }
352       default:
353         break;
354     }
355 
356     return true;
357   }
358 
InitLinuxArgs(LinuxEspBuilder & linux)359   void InitLinuxArgs(LinuxEspBuilder& linux) {
360     linux.Root("/dev/vda2");
361 
362     linux.Argument("console", "hvc0")
363          .Argument("panic", "-1")
364          .Argument("noefi");
365 
366     switch (instance_.target_arch()) {
367       case Arch::Arm:
368       case Arch::Arm64:
369         linux.Argument("console", "ttyAMA0");
370         break;
371       case Arch::RiscV64:
372         linux.Argument("console", "ttyS0");
373         break;
374       case Arch::X86:
375       case Arch::X86_64:
376         linux.Argument("console", "ttyS0")
377              .Argument("pnpacpi", "off")
378              .Argument("acpi", "noirq")
379              .Argument("reboot", "k")
380              .Argument("noexec", "off");
381         break;
382     }
383   }
384 
InitChromeOsArgs(LinuxEspBuilder & linux)385   void InitChromeOsArgs(LinuxEspBuilder& linux) {
386     linux.Root("/dev/vda2")
387         .Argument("console", "ttyS0")
388         .Argument("panic", "-1")
389         .Argument("noefi")
390         .Argument("init=/sbin/init")
391         .Argument("boot=local")
392         .Argument("rootwait")
393         .Argument("noresume")
394         .Argument("noswap")
395         .Argument("loglevel=7")
396         .Argument("noinitrd")
397         .Argument("cros_efi")
398         .Argument("cros_debug")
399         .Argument("earlyprintk=serial,ttyS0,115200")
400         .Argument("earlycon=uart8250,io,0x3f8")
401         .Argument("pnpacpi", "off")
402         .Argument("acpi", "noirq")
403         .Argument("reboot", "k")
404         .Argument("noexec", "off");
405   }
406 
407   const CuttlefishConfig& config_;
408   const CuttlefishConfig::InstanceSpecific& instance_;
409 };
410 
411 fruit::Component<fruit::Required<const CuttlefishConfig,
412                                  const CuttlefishConfig::InstanceSpecific>,
413                  InitializeEspImage>
InitializeEspImageComponent()414 InitializeEspImageComponent() {
415   return fruit::createComponent()
416       .addMultibinding<SetupFeature, InitializeEspImage>()
417       .bind<InitializeEspImage, InitializeEspImageImpl>();
418 }
419 
420 } // namespace cuttlefish
421