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