• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "host/libs/config/data_image.h"
2 
3 #include <android-base/logging.h>
4 #include <android-base/result.h>
5 
6 #include "blkid.h"
7 
8 #include "common/libs/fs/shared_buf.h"
9 #include "common/libs/utils/files.h"
10 #include "common/libs/utils/result.h"
11 #include "common/libs/utils/subprocess.h"
12 #include "host/libs/config/mbr.h"
13 #include "host/libs/vm_manager/gem5_manager.h"
14 
15 namespace cuttlefish {
16 
17 namespace {
18 const std::string kDataPolicyUseExisting = "use_existing";
19 const std::string kDataPolicyCreateIfMissing = "create_if_missing";
20 const std::string kDataPolicyAlwaysCreate = "always_create";
21 const std::string kDataPolicyResizeUpTo= "resize_up_to";
22 
23 const int FSCK_ERROR_CORRECTED = 1;
24 const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
25 
26 // Currently the Cuttlefish bootloaders are built only for x86 (32-bit),
27 // ARM (QEMU only, 32-bit) and AArch64 (64-bit), and U-Boot will hard-code
28 // these search paths. Install all bootloaders to one of these paths.
29 // NOTE: For now, just ignore the 32-bit ARM version, as Debian doesn't
30 //       build an EFI monolith for this architecture.
31 const std::string kBootPathIA32 = "EFI/BOOT/BOOTIA32.EFI";
32 const std::string kBootPathAA64 = "EFI/BOOT/BOOTAA64.EFI";
33 const std::string kM5 = "";
34 
35 // These are the paths Debian installs the monoliths to. If another distro
36 // uses an alternative monolith path, add it to this table
37 const std::pair<std::string, std::string> kGrubBlobTable[] = {
38     {"/usr/lib/grub/i386-efi/monolithic/grubia32.efi", kBootPathIA32},
39     {"/usr/lib/grub/arm64-efi/monolithic/grubaa64.efi", kBootPathAA64},
40 };
41 
42 // M5 checkpoint required binary file
43 const std::pair<std::string, std::string> kM5BlobTable[] = {
44     {"/tmp/m5", kM5},
45 };
46 
ForceFsckImage(const CuttlefishConfig & config,const std::string & data_image)47 bool ForceFsckImage(const CuttlefishConfig& config,
48                     const std::string& data_image) {
49   std::string fsck_path;
50   if (config.userdata_format() == "f2fs") {
51     fsck_path = HostBinaryPath("fsck.f2fs");
52   } else if (config.userdata_format() == "ext4") {
53     fsck_path = "/sbin/e2fsck";
54   }
55   int fsck_status = execute({fsck_path, "-y", "-f", data_image});
56   if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
57     LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code "
58                << fsck_status;
59     return false;
60   }
61   return true;
62 }
63 
NewfsMsdos(const std::string & data_image,int data_image_mb,int offset_num_mb)64 bool NewfsMsdos(const std::string& data_image, int data_image_mb,
65                 int offset_num_mb) {
66   off_t image_size_bytes = static_cast<off_t>(data_image_mb) << 20;
67   off_t offset_size_bytes = static_cast<off_t>(offset_num_mb) << 20;
68   image_size_bytes -= offset_size_bytes;
69   off_t image_size_sectors = image_size_bytes / 512;
70   auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
71   return execute({newfs_msdos_path,
72                          "-F",
73                          "32",
74                          "-m",
75                          "0xf8",
76                          "-o",
77                          "0",
78                          "-c",
79                          "8",
80                          "-h",
81                          "255",
82                          "-u",
83                          "63",
84                          "-S",
85                          "512",
86                          "-s",
87                          std::to_string(image_size_sectors),
88                          "-C",
89                          std::to_string(data_image_mb) + "M",
90                          "-@",
91                          std::to_string(offset_size_bytes),
92                          data_image}) == 0;
93 }
94 
ResizeImage(const CuttlefishConfig & config,const std::string & data_image,int data_image_mb)95 bool ResizeImage(const CuttlefishConfig& config, const std::string& data_image,
96                  int data_image_mb) {
97   auto file_mb = FileSize(data_image) >> 20;
98   if (file_mb > data_image_mb) {
99     LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
100                << "resize down.";
101     return false;
102   } else if (file_mb == data_image_mb) {
103     LOG(INFO) << data_image << " is already the right size";
104     return true;
105   } else {
106     off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
107     auto fd = SharedFD::Open(data_image, O_RDWR);
108     if (fd->Truncate(raw_target) != 0) {
109       LOG(ERROR) << "`truncate --size=" << data_image_mb << "M "
110                   << data_image << "` failed:" << fd->StrError();
111       return false;
112     }
113     bool fsck_success = ForceFsckImage(config, data_image);
114     if (!fsck_success) {
115       return false;
116     }
117     std::string resize_path;
118     if (config.userdata_format() == "f2fs") {
119       resize_path = HostBinaryPath("resize.f2fs");
120     } else if (config.userdata_format() == "ext4") {
121       resize_path = "/sbin/resize2fs";
122     }
123     int resize_status = execute({resize_path, data_image});
124     if (resize_status != 0) {
125       LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code "
126                  << resize_status;
127       return false;
128     }
129     fsck_success = ForceFsckImage(config, data_image);
130     if (!fsck_success) {
131       return false;
132     }
133   }
134   return true;
135 }
136 } // namespace
137 
CreateBlankImage(const std::string & image,int num_mb,const std::string & image_fmt)138 bool CreateBlankImage(
139     const std::string& image, int num_mb, const std::string& image_fmt) {
140   LOG(DEBUG) << "Creating " << image;
141 
142   off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
143   // The newfs_msdos tool with the mandatory -C option will do the same
144   // as below to zero the image file, so we don't need to do it here
145   if (image_fmt != "sdcard") {
146     auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
147     if (fd->Truncate(image_size_bytes) != 0) {
148       LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
149                  << "` failed:" << fd->StrError();
150       return false;
151     }
152   }
153 
154   if (image_fmt == "ext4") {
155     if (execute({"/sbin/mkfs.ext4", image}) != 0) {
156       return false;
157     }
158   } else if (image_fmt == "f2fs") {
159     auto make_f2fs_path = cuttlefish::HostBinaryPath("make_f2fs");
160     if (execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
161              "compression,extra_attr,project_quota", "-g", "android"}) != 0) {
162       return false;
163     }
164   } else if (image_fmt == "sdcard") {
165     // Reserve 1MB in the image for the MBR and padding, to simulate what
166     // other OSes do by default when partitioning a drive
167     off_t offset_size_bytes = 1 << 20;
168     image_size_bytes -= offset_size_bytes;
169     if (!NewfsMsdos(image, num_mb, 1)) {
170       LOG(ERROR) << "Failed to create SD-Card filesystem";
171       return false;
172     }
173     // Write the MBR after the filesystem is formatted, as the formatting tools
174     // don't consistently preserve the image contents
175     MasterBootRecord mbr = {
176         .partitions = {{
177             .partition_type = 0xC,
178             .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
179             .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
180         }},
181         .boot_signature = {0x55, 0xAA},
182     };
183     auto fd = SharedFD::Open(image, O_RDWR);
184     if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
185       LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
186       return false;
187     }
188   } else if (image_fmt != "none") {
189     LOG(WARNING) << "Unknown image format '" << image_fmt
190                  << "' for " << image << ", treating as 'none'.";
191   }
192   return true;
193 }
194 
GetFsType(const std::string & path)195 std::string GetFsType(const std::string& path) {
196   std::string fs_type;
197   blkid_cache cache;
198   if (blkid_get_cache(&cache, NULL) < 0) {
199     LOG(INFO) << "blkid_get_cache failed";
200     return fs_type;
201   }
202   blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
203   if (!dev) {
204     LOG(INFO) << "blkid_get_dev failed";
205     blkid_put_cache(cache);
206     return fs_type;
207   }
208 
209   const char *type, *value;
210   blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
211   while (blkid_tag_next(iter, &type, &value) == 0) {
212     if (!strcmp(type, "TYPE")) {
213       fs_type = value;
214     }
215   }
216   blkid_tag_iterate_end(iter);
217   blkid_put_cache(cache);
218   return fs_type;
219 }
220 
221 struct DataImageTag {};
222 
223 class FixedDataImagePath : public DataImagePath {
224  public:
INJECT(FixedDataImagePath (ANNOTATED (DataImageTag,std::string)path))225   INJECT(FixedDataImagePath(ANNOTATED(DataImageTag, std::string) path))
226       : path_(path) {}
227 
Path() const228   const std::string& Path() const override { return path_; }
229 
230  private:
231   std::string path_;
232 };
233 
FixedDataImagePathComponent(const std::string * path)234 fruit::Component<DataImagePath> FixedDataImagePathComponent(
235     const std::string* path) {
236   return fruit::createComponent()
237       .bind<DataImagePath, FixedDataImagePath>()
238       .bindInstance<fruit::Annotated<DataImageTag, std::string>>(*path);
239 }
240 
241 class InitializeDataImageImpl : public InitializeDataImage {
242  public:
INJECT(InitializeDataImageImpl (const CuttlefishConfig & config,DataImagePath & data_path))243   INJECT(InitializeDataImageImpl(const CuttlefishConfig& config,
244                                  DataImagePath& data_path))
245       : config_(config), data_path_(data_path) {}
246 
247   // SetupFeature
Name() const248   std::string Name() const override { return "InitializeDataImageImpl"; }
Enabled() const249   bool Enabled() const override { return true; }
250 
251  private:
Dependencies() const252   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Setup()253   bool Setup() override {
254     auto action = ChooseAction();
255     if (!action.ok()) {
256       LOG(ERROR) << "Failed to select a userdata processing action: "
257                  << action.error();
258       return false;
259     }
260     auto result = EvaluateAction(*action);
261     if (!result.ok()) {
262       LOG(ERROR) << "Failed to evaluate userdata action: " << result.error();
263       return false;
264     }
265     return true;
266   }
267 
268  private:
269   enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
270 
ChooseAction()271   Result<DataImageAction> ChooseAction() {
272     if (config_.data_policy() == kDataPolicyAlwaysCreate) {
273       return DataImageAction::kCreateImage;
274     }
275     if (!FileHasContent(data_path_.Path())) {
276       if (config_.data_policy() == kDataPolicyUseExisting) {
277         return CF_ERR("A data image must exist to use -data_policy="
278                       << kDataPolicyUseExisting);
279       } else if (config_.data_policy() == kDataPolicyResizeUpTo) {
280         return CF_ERR(data_path_.Path()
281                       << " does not exist, but resizing was requested");
282       }
283       return DataImageAction::kCreateImage;
284     }
285     if (GetFsType(data_path_.Path()) != config_.userdata_format()) {
286       CF_EXPECT(config_.data_policy() == kDataPolicyResizeUpTo,
287                 "Changing the fs format is incompatible with -data_policy="
288                     << kDataPolicyResizeUpTo);
289       return DataImageAction::kCreateImage;
290     }
291     if (config_.data_policy() == kDataPolicyResizeUpTo) {
292       return DataImageAction::kResizeImage;
293     }
294     return DataImageAction::kNoAction;
295   }
296 
EvaluateAction(DataImageAction action)297   Result<void> EvaluateAction(DataImageAction action) {
298     switch (action) {
299       case DataImageAction::kNoAction:
300         LOG(DEBUG) << data_path_.Path() << " exists. Not creating it.";
301         return {};
302       case DataImageAction::kCreateImage: {
303         RemoveFile(data_path_.Path());
304         CF_EXPECT(config_.blank_data_image_mb() != 0,
305                   "Expected `-blank_data_image_mb` to be set for "
306                   "image creation.");
307         CF_EXPECT(
308             CreateBlankImage(data_path_.Path(), config_.blank_data_image_mb(),
309                              config_.userdata_format()),
310             "Failed to create a blank image at \""
311                 << data_path_.Path() << "\" with size "
312                 << config_.blank_data_image_mb() << " and format \""
313                 << config_.userdata_format() << "\"");
314         return {};
315       }
316       case DataImageAction::kResizeImage: {
317         CF_EXPECT(config_.blank_data_image_mb() != 0,
318                   "Expected `-blank_data_image_mb` to be set for "
319                   "image resizing.");
320         CF_EXPECT(ResizeImage(config_, data_path_.Path(),
321                               config_.blank_data_image_mb()),
322                   "Failed to resize \"" << data_path_.Path() << "\" to "
323                                         << config_.blank_data_image_mb()
324                                         << " MB");
325         return {};
326       }
327     }
328   }
329 
330   const CuttlefishConfig& config_;
331   DataImagePath& data_path_;
332 };
333 
334 fruit::Component<fruit::Required<const CuttlefishConfig, DataImagePath>,
335                  InitializeDataImage>
InitializeDataImageComponent()336 InitializeDataImageComponent() {
337   return fruit::createComponent()
338       .addMultibinding<SetupFeature, InitializeDataImage>()
339       .bind<InitializeDataImage, InitializeDataImageImpl>();
340 }
341 
342 struct MiscImageTag {};
343 
344 class FixedMiscImagePath : public MiscImagePath {
345  public:
INJECT(FixedMiscImagePath (ANNOTATED (MiscImageTag,std::string)path))346   INJECT(FixedMiscImagePath(ANNOTATED(MiscImageTag, std::string) path))
347       : path_(path) {}
348 
Path() const349   const std::string& Path() const override { return path_; }
350 
351  private:
352   std::string path_;
353 };
354 
355 class InitializeMiscImageImpl : public InitializeMiscImage {
356  public:
INJECT(InitializeMiscImageImpl (MiscImagePath & misc_path))357   INJECT(InitializeMiscImageImpl(MiscImagePath& misc_path))
358       : misc_path_(misc_path) {}
359 
360   // SetupFeature
Name() const361   std::string Name() const override { return "InitializeMiscImageImpl"; }
Enabled() const362   bool Enabled() const override { return true; }
363 
364  private:
Dependencies() const365   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Setup()366   bool Setup() override {
367     bool misc_exists = FileHasContent(misc_path_.Path());
368 
369     if (misc_exists) {
370       LOG(DEBUG) << "misc partition image: use existing at \""
371                  << misc_path_.Path() << "\"";
372       return true;
373     }
374 
375     LOG(DEBUG) << "misc partition image: creating empty at \""
376                << misc_path_.Path() << "\"";
377     if (!CreateBlankImage(misc_path_.Path(), 1 /* mb */, "none")) {
378       LOG(ERROR) << "Failed to create misc image";
379       return false;
380     }
381     return true;
382   }
383 
384  private:
385   MiscImagePath& misc_path_;
386 };
387 
FixedMiscImagePathComponent(const std::string * path)388 fruit::Component<MiscImagePath> FixedMiscImagePathComponent(
389     const std::string* path) {
390   return fruit::createComponent()
391       .bind<MiscImagePath, FixedMiscImagePath>()
392       .bindInstance<fruit::Annotated<MiscImageTag, std::string>>(*path);
393 }
394 
395 fruit::Component<fruit::Required<MiscImagePath>, InitializeMiscImage>
InitializeMiscImageComponent()396 InitializeMiscImageComponent() {
397   return fruit::createComponent()
398       .addMultibinding<SetupFeature, InitializeMiscImage>()
399       .bind<InitializeMiscImage, InitializeMiscImageImpl>();
400 }
401 
402 struct EspImageTag {};
403 struct KernelPathTag {};
404 struct InitRamFsTag {};
405 struct RootFsTag {};
406 struct ConfigTag {};
407 
408 class InitializeEspImageImpl : public InitializeEspImage {
409  public:
INJECT(InitializeEspImageImpl (ANNOTATED (EspImageTag,std::string)esp_image,ANNOTATED (KernelPathTag,std::string)kernel_path,ANNOTATED (InitRamFsTag,std::string)initramfs_path,ANNOTATED (RootFsTag,std::string)rootfs_path,ANNOTATED (ConfigTag,const CuttlefishConfig *)config))410   INJECT(InitializeEspImageImpl(ANNOTATED(EspImageTag, std::string) esp_image,
411                                 ANNOTATED(KernelPathTag, std::string)
412                                     kernel_path,
413                                 ANNOTATED(InitRamFsTag, std::string)
414                                     initramfs_path,
415                                 ANNOTATED(RootFsTag, std::string) rootfs_path,
416                                 ANNOTATED(ConfigTag, const CuttlefishConfig *) config))
417       : esp_image_(esp_image),
418         kernel_path_(kernel_path),
419         initramfs_path_(initramfs_path),
420         rootfs_path_(rootfs_path),
421         config_(config){}
422 
423   // SetupFeature
Name() const424   std::string Name() const override { return "InitializeEspImageImpl"; }
Dependencies() const425   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Enabled() const426   bool Enabled() const override { return !rootfs_path_.empty(); }
427 
428  protected:
Setup()429   bool Setup() override {
430     bool esp_exists = FileHasContent(esp_image_);
431     if (esp_exists) {
432       LOG(DEBUG) << "esp partition image: use existing";
433       return true;
434     }
435 
436     LOG(DEBUG) << "esp partition image: creating default";
437 
438     // newfs_msdos won't make a partition smaller than 257 mb
439     // this should be enough for anybody..
440     auto tmp_esp_image = esp_image_ + ".tmp";
441     if (!NewfsMsdos(tmp_esp_image, 257 /* mb */, 0 /* mb (offset) */)) {
442       LOG(ERROR) << "Failed to create filesystem for " << tmp_esp_image;
443       return false;
444     }
445 
446     // For licensing and build reproducibility reasons, pick up the bootloaders
447     // from the host Linux distribution (if present) and pack them into the
448     // automatically generated ESP. If the user wants their own bootloaders,
449     // they can use -esp_image=/path/to/esp.img to override, so we don't need
450     // to accommodate customizations of this packing process.
451 
452     int success;
453     const std::pair<std::string, std::string> *kBlobTable;
454     std::size_t size;
455     // Skip GRUB on Gem5
456     if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
457       // Currently we only support Debian based distributions, and GRUB is built
458       // for those distros to always load grub.cfg from EFI/debian/grub.cfg, and
459       // nowhere else. If you want to add support for other distros, make the
460       // extra directories below and copy the initial grub.cfg there as well
461       auto mmd = HostBinaryPath("mmd");
462       success =
463           execute({mmd, "-i", tmp_esp_image, "EFI", "EFI/BOOT", "EFI/debian"});
464       if (success != 0) {
465         LOG(ERROR) << "Failed to create directories in " << tmp_esp_image;
466         return false;
467       }
468       size = sizeof(kGrubBlobTable)/sizeof(const std::pair<std::string, std::string>);
469       kBlobTable = kGrubBlobTable;
470     } else {
471       size = sizeof(kM5BlobTable)/sizeof(const std::pair<std::string, std::string>);
472       kBlobTable = kM5BlobTable;
473     }
474 
475     // The grub binaries are small, so just copy all the architecture blobs
476     // we can find, which minimizes complexity. If the user removed the grub bin
477     // package from their system, the ESP will be empty and Other OS will not be
478     // supported
479     auto mcopy = HostBinaryPath("mcopy");
480     bool copied = false;
481     for (int i=0; i<size; i++) {
482       auto grub = kBlobTable[i];
483       if (!FileExists(grub.first)) {
484         continue;
485       }
486       success = execute({mcopy, "-o", "-i", tmp_esp_image, "-s", grub.first,
487                          "::" + grub.second});
488       if (success != 0) {
489         LOG(ERROR) << "Failed to copy " << grub.first << " to " << grub.second
490                    << " in " << tmp_esp_image;
491         return false;
492       }
493       copied = true;
494     }
495 
496     if (!copied) {
497       LOG(ERROR) << "Binary dependencies were not found on this system; Other OS "
498                     "support will be broken";
499       return false;
500     }
501 
502     // Skip Gem5 case. Gem5 will never be able to use bootloaders like grub.
503     if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
504       auto grub_cfg = DefaultHostArtifactsPath("etc/grub/grub.cfg");
505       CHECK(FileExists(grub_cfg)) << "Missing file " << grub_cfg << "!";
506       success =
507           execute({mcopy, "-i", tmp_esp_image, "-s", grub_cfg, "::EFI/debian/"});
508       if (success != 0) {
509         LOG(ERROR) << "Failed to copy " << grub_cfg << " to " << tmp_esp_image;
510         return false;
511       }
512     }
513 
514     if (!kernel_path_.empty()) {
515       success = execute(
516           {mcopy, "-i", tmp_esp_image, "-s", kernel_path_, "::vmlinuz"});
517       if (success != 0) {
518         LOG(ERROR) << "Failed to copy " << kernel_path_ << " to "
519                    << tmp_esp_image;
520         return false;
521       }
522 
523       if (!initramfs_path_.empty()) {
524         success = execute({mcopy, "-i", tmp_esp_image, "-s", initramfs_path_,
525                            "::initrd.img"});
526         if (success != 0) {
527           LOG(ERROR) << "Failed to copy " << initramfs_path_ << " to "
528                      << tmp_esp_image;
529           return false;
530         }
531       }
532     }
533 
534     if (!cuttlefish::RenameFile(tmp_esp_image, esp_image_)) {
535       LOG(ERROR) << "Renaming " << tmp_esp_image << " to " << esp_image_
536                  << " failed";
537       return false;
538     }
539     return true;
540   }
541 
542  private:
543   std::string esp_image_;
544   std::string kernel_path_;
545   std::string initramfs_path_;
546   std::string rootfs_path_;
547   const CuttlefishConfig* config_;
548 };
549 
550 fruit::Component<fruit::Required<const CuttlefishConfig>,
InitializeEspImageComponent(const std::string * esp_image,const std::string * kernel_path,const std::string * initramfs_path,const std::string * rootfs_path,const CuttlefishConfig * config)551     InitializeEspImage> InitializeEspImageComponent(
552     const std::string* esp_image, const std::string* kernel_path,
553     const std::string* initramfs_path, const std::string* rootfs_path,
554     const CuttlefishConfig* config) {
555   return fruit::createComponent()
556       .addMultibinding<SetupFeature, InitializeEspImage>()
557       .bind<InitializeEspImage, InitializeEspImageImpl>()
558       .bindInstance<fruit::Annotated<EspImageTag, std::string>>(*esp_image)
559       .bindInstance<fruit::Annotated<KernelPathTag, std::string>>(*kernel_path)
560       .bindInstance<fruit::Annotated<InitRamFsTag, std::string>>(
561           *initramfs_path)
562       .bindInstance<fruit::Annotated<RootFsTag, std::string>>(*rootfs_path)
563       .bindInstance<fruit::Annotated<ConfigTag, CuttlefishConfig>>(*config);
564 }
565 
566 } // namespace cuttlefish
567