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/network.h"
26 #include "common/libs/utils/result.h"
27 #include "common/libs/utils/subprocess.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 const std::string kDataPolicyUseExisting = "use_existing";
37 const std::string kDataPolicyCreateIfMissing = "create_if_missing";
38 const std::string kDataPolicyAlwaysCreate = "always_create";
39 const std::string 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 bool ForceFsckImage(const std::string& data_image,
45                     const CuttlefishConfig::InstanceSpecific& instance) {
46   std::string fsck_path;
47   if (instance.userdata_format() == "f2fs") {
48     fsck_path = HostBinaryPath("fsck.f2fs");
49   } else if (instance.userdata_format() == "ext4") {
50     fsck_path = "/sbin/e2fsck";
51   }
52   int fsck_status = execute({fsck_path, "-y", "-f", data_image});
53   if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
54     LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code "
55                << fsck_status;
56     return false;
57   }
58   return true;
59 }
60 
ResizeImage(const std::string & data_image,int data_image_mb,const CuttlefishConfig::InstanceSpecific & instance)61 bool ResizeImage(const std::string& data_image, int data_image_mb,
62                  const CuttlefishConfig::InstanceSpecific& instance) {
63   auto file_mb = FileSize(data_image) >> 20;
64   if (file_mb > data_image_mb) {
65     LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
66                << "resize down.";
67     return false;
68   } else if (file_mb == data_image_mb) {
69     LOG(INFO) << data_image << " is already the right size";
70     return true;
71   } else {
72     off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
73     auto fd = SharedFD::Open(data_image, O_RDWR);
74     if (fd->Truncate(raw_target) != 0) {
75       LOG(ERROR) << "`truncate --size=" << data_image_mb << "M "
76                   << data_image << "` failed:" << fd->StrError();
77       return false;
78     }
79     bool fsck_success = ForceFsckImage(data_image, instance);
80     if (!fsck_success) {
81       return false;
82     }
83     std::string resize_path;
84     if (instance.userdata_format() == "f2fs") {
85       resize_path = HostBinaryPath("resize.f2fs");
86     } else if (instance.userdata_format() == "ext4") {
87       resize_path = "/sbin/resize2fs";
88     }
89     int resize_status = execute({resize_path, data_image});
90     if (resize_status != 0) {
91       LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code "
92                  << resize_status;
93       return false;
94     }
95     fsck_success = ForceFsckImage(data_image, instance);
96     if (!fsck_success) {
97       return false;
98     }
99   }
100   return true;
101 }
102 } // namespace
103 
CreateBlankImage(const std::string & image,int num_mb,const std::string & image_fmt)104 bool CreateBlankImage(
105     const std::string& image, int num_mb, const std::string& image_fmt) {
106   LOG(DEBUG) << "Creating " << image;
107 
108   off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
109   // The newfs_msdos tool with the mandatory -C option will do the same
110   // as below to zero the image file, so we don't need to do it here
111   if (image_fmt != "sdcard") {
112     auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
113     if (fd->Truncate(image_size_bytes) != 0) {
114       LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
115                  << "` failed:" << fd->StrError();
116       return false;
117     }
118   }
119 
120   if (image_fmt == "ext4") {
121     if (execute({"/sbin/mkfs.ext4", image}) != 0) {
122       return false;
123     }
124   } else if (image_fmt == "f2fs") {
125     auto make_f2fs_path = HostBinaryPath("make_f2fs");
126     if (execute({make_f2fs_path, "-l", "data", image, "-C", "utf8", "-O",
127      "compression,extra_attr,project_quota,casefold", "-g", "android"}) != 0) {
128       return false;
129     }
130   } else if (image_fmt == "sdcard") {
131     // Reserve 1MB in the image for the MBR and padding, to simulate what
132     // other OSes do by default when partitioning a drive
133     off_t offset_size_bytes = 1 << 20;
134     image_size_bytes -= offset_size_bytes;
135     if (!NewfsMsdos(image, num_mb, 1)) {
136       LOG(ERROR) << "Failed to create SD-Card filesystem";
137       return false;
138     }
139     // Write the MBR after the filesystem is formatted, as the formatting tools
140     // don't consistently preserve the image contents
141     MasterBootRecord mbr = {
142         .partitions = {{
143             .partition_type = 0xC,
144             .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
145             .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
146         }},
147         .boot_signature = {0x55, 0xAA},
148     };
149     auto fd = SharedFD::Open(image, O_RDWR);
150     if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
151       LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
152       return false;
153     }
154   } else if (image_fmt != "none") {
155     LOG(WARNING) << "Unknown image format '" << image_fmt
156                  << "' for " << image << ", treating as 'none'.";
157   }
158   return true;
159 }
160 
GetFsType(const std::string & path)161 std::string GetFsType(const std::string& path) {
162   std::string fs_type;
163   blkid_cache cache;
164   if (blkid_get_cache(&cache, NULL) < 0) {
165     LOG(INFO) << "blkid_get_cache failed";
166     return fs_type;
167   }
168   blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
169   if (!dev) {
170     LOG(INFO) << "blkid_get_dev failed";
171     blkid_put_cache(cache);
172     return fs_type;
173   }
174 
175   const char *type, *value;
176   blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
177   while (blkid_tag_next(iter, &type, &value) == 0) {
178     if (!strcmp(type, "TYPE")) {
179       fs_type = value;
180     }
181   }
182   blkid_tag_iterate_end(iter);
183   blkid_put_cache(cache);
184   return fs_type;
185 }
186 
187 class InitializeDataImageImpl : public InitializeDataImage {
188  public:
INJECT(InitializeDataImageImpl (const CuttlefishConfig::InstanceSpecific & instance))189   INJECT(InitializeDataImageImpl(
190       const CuttlefishConfig::InstanceSpecific& instance))
191       : instance_(instance) {}
192 
193   // SetupFeature
Name() const194   std::string Name() const override { return "InitializeDataImageImpl"; }
Enabled() const195   bool Enabled() const override { return true; }
196 
197  private:
Dependencies() const198   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Setup()199   bool Setup() override {
200     auto action = ChooseAction();
201     if (!action.ok()) {
202       LOG(ERROR) << "Failed to select a userdata processing action: "
203                  << action.error().Message();
204       LOG(DEBUG) << "Failed to select a userdata processing action: "
205                  << action.error().Trace();
206       return false;
207     }
208     auto result = EvaluateAction(*action);
209     if (!result.ok()) {
210       LOG(ERROR) << "Failed to evaluate userdata action: "
211                  << result.error().Message();
212       LOG(DEBUG) << "Failed to evaluate userdata action: "
213                  << result.error().Trace();
214       return false;
215     }
216     return true;
217   }
218 
219  private:
220   enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
221 
ChooseAction()222   Result<DataImageAction> ChooseAction() {
223     if (instance_.data_policy() == kDataPolicyAlwaysCreate) {
224       return DataImageAction::kCreateImage;
225     }
226     if (!FileHasContent(instance_.data_image())) {
227       if (instance_.data_policy() == kDataPolicyUseExisting) {
228         return CF_ERR("A data image must exist to use -data_policy="
229                       << kDataPolicyUseExisting);
230       } else if (instance_.data_policy() == kDataPolicyResizeUpTo) {
231         return CF_ERR(instance_.data_image()
232                       << " does not exist, but resizing was requested");
233       }
234       return DataImageAction::kCreateImage;
235     }
236     if (instance_.data_policy() == kDataPolicyUseExisting) {
237       return DataImageAction::kNoAction;
238     }
239     auto current_fs_type = GetFsType(instance_.data_image());
240     if (current_fs_type != instance_.userdata_format()) {
241       CF_EXPECT(instance_.data_policy() != kDataPolicyResizeUpTo,
242                 "Changing the fs format is incompatible with -data_policy="
243                     << kDataPolicyResizeUpTo << " (\"" << current_fs_type
244                     << "\" != \"" << instance_.userdata_format() << "\")");
245       return DataImageAction::kCreateImage;
246     }
247     if (instance_.data_policy() == kDataPolicyResizeUpTo) {
248       return DataImageAction::kResizeImage;
249     }
250     return DataImageAction::kNoAction;
251   }
252 
EvaluateAction(DataImageAction action)253   Result<void> EvaluateAction(DataImageAction action) {
254     switch (action) {
255       case DataImageAction::kNoAction:
256         LOG(DEBUG) << instance_.data_image() << " exists. Not creating it.";
257         return {};
258       case DataImageAction::kCreateImage: {
259         RemoveFile(instance_.data_image());
260         CF_EXPECT(instance_.blank_data_image_mb() != 0,
261                   "Expected `-blank_data_image_mb` to be set for "
262                   "image creation.");
263         CF_EXPECT(CreateBlankImage(instance_.data_image(),
264                                    instance_.blank_data_image_mb(),
265                                    instance_.userdata_format()),
266                   "Failed to create a blank image at \""
267                       << instance_.data_image() << "\" with size "
268                       << instance_.blank_data_image_mb() << " and format \""
269                       << instance_.userdata_format() << "\"");
270         return {};
271       }
272       case DataImageAction::kResizeImage: {
273         CF_EXPECT(instance_.blank_data_image_mb() != 0,
274                   "Expected `-blank_data_image_mb` to be set for "
275                   "image resizing.");
276         CF_EXPECT(ResizeImage(instance_.data_image(),
277                               instance_.blank_data_image_mb(), instance_),
278                   "Failed to resize \"" << instance_.data_image() << "\" to "
279                                         << instance_.blank_data_image_mb()
280                                         << " MB");
281         return {};
282       }
283     }
284   }
285 
286   const CuttlefishConfig::InstanceSpecific& instance_;
287 };
288 
289 fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
290                  InitializeDataImage>
InitializeDataImageComponent()291 InitializeDataImageComponent() {
292   return fruit::createComponent()
293       .addMultibinding<SetupFeature, InitializeDataImage>()
294       .bind<InitializeDataImage, InitializeDataImageImpl>();
295 }
296 
297 class InitializeMiscImageImpl : public InitializeMiscImage {
298  public:
INJECT(InitializeMiscImageImpl (const CuttlefishConfig::InstanceSpecific & instance))299   INJECT(InitializeMiscImageImpl(
300       const CuttlefishConfig::InstanceSpecific& instance))
301       : instance_(instance) {}
302 
303   // SetupFeature
Name() const304   std::string Name() const override { return "InitializeMiscImageImpl"; }
Enabled() const305   bool Enabled() const override { return true; }
306 
307  private:
Dependencies() const308   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Setup()309   bool Setup() override {
310     bool misc_exists = FileHasContent(instance_.misc_image());
311 
312     if (misc_exists) {
313       LOG(DEBUG) << "misc partition image: use existing at \""
314                  << instance_.misc_image() << "\"";
315       return true;
316     }
317 
318     LOG(DEBUG) << "misc partition image: creating empty at \""
319                << instance_.misc_image() << "\"";
320     if (!CreateBlankImage(instance_.new_misc_image(), 1 /* mb */, "none")) {
321       LOG(ERROR) << "Failed to create misc image";
322       return false;
323     }
324     return true;
325   }
326 
327  private:
328   const CuttlefishConfig::InstanceSpecific& instance_;
329 };
330 
331 fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
332                  InitializeMiscImage>
InitializeMiscImageComponent()333 InitializeMiscImageComponent() {
334   return fruit::createComponent()
335       .addMultibinding<SetupFeature, InitializeMiscImage>()
336       .bind<InitializeMiscImage, InitializeMiscImageImpl>();
337 }
338 
339 class InitializeEspImageImpl : public InitializeEspImage {
340  public:
INJECT(InitializeEspImageImpl (const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance))341   INJECT(InitializeEspImageImpl(
342       const CuttlefishConfig& config,
343       const CuttlefishConfig::InstanceSpecific& instance))
344       : config_(config), instance_(instance) {}
345 
346   // SetupFeature
Name() const347   std::string Name() const override { return "InitializeEspImageImpl"; }
Dependencies() const348   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
349 
Enabled() const350   bool Enabled() const override {
351     return EspRequiredForBootFlow() || EspRequiredForAPBootFlow();
352   }
353 
354  protected:
Setup()355   bool Setup() override {
356     if (EspRequiredForAPBootFlow()) {
357       LOG(DEBUG) << "creating esp_image: " << instance_.ap_esp_image_path();
358       if (!BuildAPImage()) {
359         return false;
360       }
361     }
362     const auto is_not_gem5 = config_.vm_manager() != vm_manager::Gem5Manager::name();
363     const auto esp_required_for_boot_flow = EspRequiredForBootFlow();
364     if (is_not_gem5 && esp_required_for_boot_flow) {
365       LOG(DEBUG) << "creating esp_image: " << instance_.otheros_esp_image_path();
366       if (!BuildOSImage()) {
367         return false;
368       }
369     }
370 
371     return true;
372   }
373 
374  private:
375 
EspRequiredForBootFlow() const376   bool EspRequiredForBootFlow() const {
377     const auto flow = instance_.boot_flow();
378     return flow == CuttlefishConfig::InstanceSpecific::BootFlow::Linux ||
379         flow == CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia;
380   }
381 
EspRequiredForAPBootFlow() const382   bool EspRequiredForAPBootFlow() const {
383     return instance_.ap_boot_flow() == CuttlefishConfig::InstanceSpecific::APBootFlow::Grub;
384   }
385 
BuildAPImage()386   bool BuildAPImage() {
387     auto linux = LinuxEspBuilder(instance_.ap_esp_image_path());
388     InitLinuxArgs(linux);
389 
390     auto openwrt_args = OpenwrtArgsFromConfig(instance_);
391     for (auto& openwrt_arg : openwrt_args) {
392       linux.Argument(openwrt_arg.first, openwrt_arg.second);
393     }
394 
395     linux.Root("/dev/vda2")
396          .Architecture(instance_.target_arch())
397          .Kernel(config_.ap_kernel_image());
398 
399     return linux.Build();
400   }
401 
BuildOSImage()402   bool BuildOSImage() {
403     switch (instance_.boot_flow()) {
404       case CuttlefishConfig::InstanceSpecific::BootFlow::Linux: {
405         auto linux = LinuxEspBuilder(instance_.otheros_esp_image_path());
406         InitLinuxArgs(linux);
407 
408         linux.Root("/dev/vda2")
409              .Architecture(instance_.target_arch())
410              .Kernel(instance_.linux_kernel_path());
411 
412         if (!instance_.linux_initramfs_path().empty()) {
413           linux.Initrd(instance_.linux_initramfs_path());
414         }
415 
416         return linux.Build();
417       }
418       case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia: {
419         auto fuchsia = FuchsiaEspBuilder(instance_.otheros_esp_image_path());
420         return fuchsia.Architecture(instance_.target_arch())
421                       .Zedboot(instance_.fuchsia_zedboot_path())
422                       .MultibootBinary(instance_.fuchsia_multiboot_bin_path())
423                       .Build();
424       }
425       default:
426         break;
427     }
428 
429     return true;
430   }
431 
InitLinuxArgs(LinuxEspBuilder & linux)432   void InitLinuxArgs(LinuxEspBuilder& linux) {
433     linux.Root("/dev/vda2");
434 
435     linux.Argument("console", "hvc0")
436          .Argument("panic", "-1")
437          .Argument("noefi");
438 
439     switch (instance_.target_arch()) {
440       case Arch::Arm:
441       case Arch::Arm64:
442         linux.Argument("console", "ttyAMA0");
443         break;
444       case Arch::RiscV64:
445         linux.Argument("console", "ttyS0");
446         break;
447       case Arch::X86:
448       case Arch::X86_64:
449         linux.Argument("console", "ttyS0")
450              .Argument("pnpacpi", "off")
451              .Argument("acpi", "noirq")
452              .Argument("reboot", "k")
453              .Argument("noexec", "off");
454         break;
455     }
456   }
457 
458   const CuttlefishConfig& config_;
459   const CuttlefishConfig::InstanceSpecific& instance_;
460 };
461 
462 fruit::Component<fruit::Required<const CuttlefishConfig,
463                                  const CuttlefishConfig::InstanceSpecific>,
464                  InitializeEspImage>
InitializeEspImageComponent()465 InitializeEspImageComponent() {
466   return fruit::createComponent()
467       .addMultibinding<SetupFeature, InitializeEspImage>()
468       .bind<InitializeEspImage, InitializeEspImageImpl>();
469 }
470 
471 } // namespace cuttlefish
472