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