1 //
2 // Copyright (C) 2022 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 <algorithm>
17 #include <array>
18 #include <sstream>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "host/libs/config/esp.h"
24 #include "common/libs/fs/shared_buf.h"
25 #include "common/libs/utils/subprocess.h"
26 #include "common/libs/utils/files.h"
27 #include "host/libs/config/cuttlefish_config.h"
28 
29 namespace cuttlefish {
30 
31 // For licensing and build reproducibility reasons, pick up the bootloaders
32 // from the host Linux distribution (if present) and pack them into the
33 // automatically generated ESP. If the user wants their own bootloaders,
34 // they can use -esp_image=/path/to/esp.img to override, so we don't need
35 // to accommodate customizations of this packing process.
36 
37 // Currently we only support Debian based distributions, and GRUB is built
38 // for those distros to always load grub.cfg from EFI/debian/grub.cfg, and
39 // nowhere else. If you want to add support for other distros, make the
40 // extra directories below and copy the initial grub.cfg there as well
41 //
42 // Currently the Cuttlefish bootloaders are built only for x86 (32-bit),
43 // ARM (QEMU only, 32-bit) and AArch64 (64-bit), and U-Boot will hard-code
44 // these search paths. Install all bootloaders to one of these paths.
45 // NOTE: For now, just ignore the 32-bit ARM version, as Debian doesn't
46 //       build an EFI monolith for this architecture.
47 // These are the paths Debian installs the monoliths to. If another distro
48 // uses an alternative monolith path, add it to this table
49 static constexpr char kBootSrcPathIA32[] =
50     "/usr/lib/grub/i386-efi/monolithic/grubia32.efi";
51 static constexpr char kBootDestPathIA32[] = "/EFI/BOOT/BOOTIA32.EFI";
52 
53 static constexpr char kBootSrcPathAA64[] =
54     "/usr/lib/grub/arm64-efi/monolithic/grubaa64.efi";
55 static constexpr char kBootDestPathAA64[] = "/EFI/BOOT/BOOTAA64.EFI";
56 
57 static constexpr char kBootDestPathRiscV64[] = "/EFI/BOOT/BOOTRISCV64.EFI";
58 
59 static constexpr char kMultibootModuleSrcPathIA32[] =
60     "/usr/lib/grub/i386-efi/multiboot.mod";
61 static constexpr char kMultibootModuleDestPathIA32[] =
62     "/EFI/modules/multiboot.mod";
63 
64 static constexpr char kMultibootModuleSrcPathAA64[] =
65     "/usr/lib/grub/arm64-efi/multiboot.mod";
66 static constexpr char kMultibootModuleDestPathAA64[] =
67     "/EFI/modules/multiboot.mod";
68 
69 static constexpr char kKernelDestPath[] = "/vmlinuz";
70 static constexpr char kInitrdDestPath[] = "/initrd";
71 static constexpr char kZedbootDestPath[] = "/zedboot.zbi";
72 static constexpr char kMultibootBinDestPath[] = "/multiboot.bin";
73 
74 // TODO(b/260338443, b/260337906) remove ubuntu and debian variations
75 // after migrating to grub-mkimage or adding grub binaries as a prebuilt
76 static constexpr char kGrubDebianConfigDestPath[] = "/EFI/debian/grub.cfg";
77 static constexpr char kGrubUbuntuConfigDestPath[] = "/EFI/ubuntu/grub.cfg";
78 static constexpr char kGrubConfigDestDirectoryPath[] = "/boot/grub";
79 static constexpr char kGrubConfigDestPath[] = "/boot/grub/grub.cfg";
80 
81 static constexpr std::array kGrubModulesX86{
82     "normal", "configfile", "linux", "linuxefi",   "multiboot", "ls",
83     "cat",    "help",       "fat",   "part_msdos", "part_gpt"};
84 static constexpr char kGrubModulesPath[] = "/usr/lib/grub/";
85 static constexpr char kGrubModulesX86Name[] = "i386-efi";
86 
NewfsMsdos(const std::string & data_image,int data_image_mb,int offset_num_mb)87 bool NewfsMsdos(const std::string& data_image, int data_image_mb,
88                 int offset_num_mb) {
89   off_t image_size_bytes = static_cast<off_t>(data_image_mb) << 20;
90   off_t offset_size_bytes = static_cast<off_t>(offset_num_mb) << 20;
91   image_size_bytes -= offset_size_bytes;
92   off_t image_size_sectors = image_size_bytes / 512;
93   auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
94   return Execute({newfs_msdos_path,
95                   "-F",
96                   "32",
97                   "-m",
98                   "0xf8",
99                   "-o",
100                   "0",
101                   "-c",
102                   "8",
103                   "-h",
104                   "255",
105                   "-u",
106                   "63",
107                   "-S",
108                   "512",
109                   "-s",
110                   std::to_string(image_size_sectors),
111                   "-C",
112                   std::to_string(data_image_mb) + "M",
113                   "-@",
114                   std::to_string(offset_size_bytes),
115                   data_image}) == 0;
116 }
117 
CanGenerateEsp(Arch arch)118 bool CanGenerateEsp(Arch arch) {
119   switch (arch) {
120     case Arch::Arm:
121     case Arch::Arm64:
122     case Arch::RiscV64:
123       // TODO(b/260960328) : Migrate openwrt image for arm64 into
124       // APBootFlow::Grub.
125       return false;
126     case Arch::X86:
127     case Arch::X86_64: {
128       const auto x86_modules = std::string(kGrubModulesPath) + std::string(kGrubModulesX86Name);
129       const auto modules_presented = std::all_of(
130           kGrubModulesX86.begin(), kGrubModulesX86.end(),
131           [&](const std::string& m) { return FileExists(x86_modules + m); });
132       if (modules_presented) {
133         return true;
134       }
135 
136       const auto monolith_presented = FileExists(kBootSrcPathIA32);
137       return monolith_presented;
138     }
139   }
140 
141   return false;
142 }
143 
MsdosMakeDirectories(const std::string & image_path,const std::vector<std::string> & directories)144 static bool MsdosMakeDirectories(const std::string& image_path,
145                                  const std::vector<std::string>& directories) {
146   auto mmd = HostBinaryPath("mmd");
147   std::vector<std::string> command {mmd, "-i", image_path};
148   command.insert(command.end(), directories.begin(), directories.end());
149 
150   const auto success = Execute(command);
151   if (success != 0) {
152     return false;
153   }
154   return true;
155 }
156 
CopyToMsdos(const std::string & image,const std::string & path,const std::string & destination)157 static bool CopyToMsdos(const std::string& image, const std::string& path,
158                         const std::string& destination) {
159   const auto mcopy = HostBinaryPath("mcopy");
160   const auto success =
161       Execute({mcopy, "-o", "-i", image, "-s", path, destination});
162   if (success != 0) {
163     return false;
164   }
165   return true;
166 }
167 
168 template <typename T>
GrubMakeImage(const std::string & prefix,const std::string & format,const std::string & directory,const std::string & output,const T & modules)169 static bool GrubMakeImage(const std::string& prefix, const std::string& format,
170                           const std::string& directory,
171                           const std::string& output, const T& modules) {
172   std::vector<std::string> command = {"grub-mkimage", "--prefix", prefix,
173                                       "--format", format, "--directory", directory,
174                                       "--output", output};
175   std::move(modules.begin(), modules.end(), std::back_inserter(command));
176 
177   const auto success = Execute(command);
178   return success == 0;
179 }
180 
181 class EspBuilder final {
182  public:
EspBuilder()183   EspBuilder() {}
EspBuilder(std::string image_path)184   EspBuilder(std::string image_path): image_path_(std::move(image_path)) {}
185 
File(std::string from,std::string to,bool required)186   EspBuilder& File(std::string from, std::string to, bool required) & {
187     files_.push_back(FileToAdd {std::move(from), std::move(to), required});
188     return *this;
189   }
190 
File(std::string from,std::string to)191   EspBuilder& File(std::string from, std::string to) & {
192     return File(std::move(from), std::move(to), false);
193   }
194 
Directory(std::string path)195   EspBuilder& Directory(std::string path) & {
196     directories_.push_back(std::move(path));
197     return *this;
198   }
199 
Merge(EspBuilder builder)200   EspBuilder& Merge(EspBuilder builder) & {
201     std::move(builder.directories_.begin(), builder.directories_.end(),
202               std::back_inserter(directories_));
203     std::move(builder.files_.begin(), builder.files_.end(),
204               std::back_inserter(files_));
205     return *this;
206   }
207 
Build()208   bool Build() {
209     if (image_path_.empty()) {
210       LOG(ERROR) << "Image path is required to build ESP. Empty constructor is intended to "
211                  << "be used only for the merge functionality";
212       return false;
213     }
214 
215     // newfs_msdos won't make a partition smaller than 257 mb
216     // this should be enough for anybody..
217     const auto tmp_esp_image = image_path_ + ".tmp";
218     if (!NewfsMsdos(tmp_esp_image, 257 /* mb */, 0 /* mb (offset) */)) {
219       LOG(ERROR) << "Failed to create filesystem for " << tmp_esp_image;
220       return false;
221     }
222 
223     if (!MsdosMakeDirectories(tmp_esp_image, directories_)) {
224       LOG(ERROR) << "Failed to create directories in " << tmp_esp_image;
225       return false;
226     }
227 
228     for (const FileToAdd& file : files_) {
229       if (!FileExists(file.from)) {
230         if (file.required) {
231           LOG(ERROR) << "Failed to copy " << file.from << " to " << tmp_esp_image
232                     << ": File does not exist";
233           return false;
234         }
235         continue;
236       }
237 
238       if (!CopyToMsdos(tmp_esp_image, file.from, "::" + file.to)) {
239         LOG(ERROR) << "Failed to copy " << file.from << " to " << tmp_esp_image
240                   << ": mcopy execution failed";
241         return false;
242       }
243     }
244 
245     if (!RenameFile(tmp_esp_image, image_path_).ok()) {
246       LOG(ERROR) << "Renaming " << tmp_esp_image << " to "
247                   << image_path_ << " failed";
248       return false;
249     }
250 
251     return true;
252   }
253 
254  private:
255   const std::string image_path_;
256 
257   struct FileToAdd {
258     std::string from;
259     std::string to;
260     bool required;
261   };
262   std::vector<std::string> directories_;
263   std::vector<FileToAdd> files_;
264 };
265 
PrepareESP(const std::string & image_path,Arch arch)266 EspBuilder PrepareESP(const std::string& image_path, Arch arch) {
267   auto builder = EspBuilder(image_path);
268   builder.Directory("EFI")
269          .Directory("EFI/BOOT")
270          .Directory("EFI/modules");
271 
272   const auto efi_path = image_path + ".efi";
273   switch (arch) {
274     case Arch::Arm:
275     case Arch::Arm64:
276       builder.File(kBootSrcPathAA64, kBootDestPathAA64, /* required */ true);
277       // Not required for arm64 due missing it in deb package, so fuchsia is
278       // not supported for it.
279       builder.File(kMultibootModuleSrcPathAA64, kMultibootModuleDestPathAA64,
280                     /* required */ false);
281       break;
282     case Arch::RiscV64:
283       // FIXME: Implement
284       break;
285     case Arch::X86:
286     case Arch::X86_64: {
287       const auto x86_modules = std::string(kGrubModulesPath) + std::string(kGrubModulesX86Name);
288 
289       if (GrubMakeImage(kGrubConfigDestDirectoryPath, kGrubModulesX86Name,
290                         x86_modules, efi_path, kGrubModulesX86)) {
291         LOG(INFO) << "Loading grub_mkimage generated EFI binary";
292         builder.File(efi_path, kBootDestPathIA32, /* required */ true);
293       } else {
294         LOG(INFO) << "Loading prebuilt monolith EFI binary";
295         builder.File(kBootSrcPathIA32, kBootDestPathIA32, /* required */ true);
296         builder.File(kMultibootModuleSrcPathIA32, kMultibootModuleDestPathIA32,
297                      /* required */ true);
298       }
299       break;
300     }
301   }
302 
303   return std::move(builder);
304 }
305 
306 // TODO(b/260338443, b/260337906) remove ubuntu and debian variations
307 // after migrating to grub-mkimage or adding grub binaries as a prebuilt
AddGrubConfig(const std::string & config)308 EspBuilder AddGrubConfig(const std::string& config) {
309   auto builder = EspBuilder();
310 
311   builder.Directory("boot")
312          .Directory("EFI/debian")
313          .Directory("EFI/ubuntu")
314          .Directory("boot/grub");
315 
316   builder.File(config, kGrubDebianConfigDestPath, /*required*/ true)
317          .File(config, kGrubUbuntuConfigDestPath, /*required*/ true)
318          .File(config, kGrubConfigDestPath, /*required*/ true);
319 
320   return builder;
321 }
322 
EfiLoaderPath(std::string efi_loader_path)323 AndroidEfiLoaderEspBuilder& AndroidEfiLoaderEspBuilder::EfiLoaderPath(
324     std::string efi_loader_path) & {
325   efi_loader_path_ = efi_loader_path;
326   return *this;
327 }
328 
Architecture(Arch arch)329 AndroidEfiLoaderEspBuilder& AndroidEfiLoaderEspBuilder::Architecture(
330     Arch arch) & {
331   arch_ = arch;
332   return *this;
333 }
334 
Build() const335 bool AndroidEfiLoaderEspBuilder::Build() const {
336   if (efi_loader_path_.empty()) {
337     LOG(ERROR)
338         << "Efi loader is required argument for AndroidEfiLoaderEspBuilder";
339     return false;
340   }
341   EspBuilder builder = EspBuilder(image_path_);
342   builder.Directory("EFI").Directory("EFI/BOOT");
343   std::string dest_path;
344   switch (arch_) {
345     case Arch::Arm:
346     case Arch::Arm64:
347       dest_path = kBootDestPathAA64;
348       break;
349     case Arch::RiscV64:
350       dest_path = kBootDestPathRiscV64;
351       break;
352     case Arch::X86:
353     case Arch::X86_64: {
354       dest_path = kBootDestPathIA32;
355       break;
356       default:
357         LOG(ERROR) << "Unknown architecture";
358         return false;
359     }
360   }
361   builder.File(efi_loader_path_, dest_path, /* required */ true);
362   return builder.Build();
363 }
364 
Argument(std::string key,std::string value)365 LinuxEspBuilder& LinuxEspBuilder::Argument(std::string key, std::string value) & {
366   arguments_.push_back({std::move(key), std::move(value)});
367   return *this;
368 }
369 
Argument(std::string value)370 LinuxEspBuilder& LinuxEspBuilder::Argument(std::string value) & {
371   single_arguments_.push_back(std::move(value));
372   return *this;
373 }
374 
Root(std::string root)375 LinuxEspBuilder& LinuxEspBuilder::Root(std::string root) & {
376   root_ = std::move(root);
377   return *this;
378 }
379 
Kernel(std::string kernel)380 LinuxEspBuilder& LinuxEspBuilder::Kernel(std::string kernel) & {
381   kernel_ = std::move(kernel);
382   return *this;
383 }
384 
Initrd(std::string initrd)385 LinuxEspBuilder& LinuxEspBuilder::Initrd(std::string initrd) & {
386   initrd_ = std::move(initrd);
387   return *this;
388 }
389 
Architecture(Arch arch)390 LinuxEspBuilder& LinuxEspBuilder::Architecture(Arch arch) & {
391   arch_ = arch;
392   return *this;
393 }
394 
Build() const395 bool LinuxEspBuilder::Build() const {
396   if (root_.empty()) {
397     LOG(ERROR) << "Root is required argument for LinuxEspBuilder";
398     return false;
399   }
400   if (kernel_.empty()) {
401     LOG(ERROR) << "Kernel esp path is required argument for LinuxEspBuilder";
402     return false;
403   }
404   if (!arch_) {
405     LOG(ERROR) << "Architecture is required argument for LinuxEspBuilder";
406     return false;
407   }
408 
409   auto builder = PrepareESP(image_path_, *arch_);
410 
411   const auto tmp_grub_config = image_path_ + ".grub.cfg";
412   const auto config_file = SharedFD::Creat(tmp_grub_config, 0644);
413   if (!config_file->IsOpen()) {
414     LOG(ERROR) << "Cannot create temporary grub config: " << tmp_grub_config;
415     return false;
416   }
417 
418   const auto dumped = DumpConfig();
419   if (WriteAll(config_file, dumped) != dumped.size()) {
420     LOG(ERROR) << "Failed to write grub config content to: " << tmp_grub_config;
421     return false;
422   }
423 
424   builder.Merge(AddGrubConfig(tmp_grub_config));
425   builder.File(kernel_, kKernelDestPath, /*required*/ true);
426   if (!initrd_.empty()) {
427     builder.File(initrd_, kInitrdDestPath, /*required*/ true);
428   }
429 
430   return builder.Build();
431 }
432 
DumpConfig() const433 std::string LinuxEspBuilder::DumpConfig() const {
434   std::ostringstream o;
435 
436   o << "set timeout=0" << std::endl
437     << "menuentry \"Linux\" {" << std::endl
438     << "  linux " << kKernelDestPath << " ";
439 
440   for (int i = 0; i < arguments_.size(); i++) {
441     o << arguments_[i].first << "=" << arguments_[i].second << " ";
442   }
443   for (int i = 0; i < single_arguments_.size(); i++) {
444     o << single_arguments_[i] << " ";
445   }
446   o << "root=" << root_ << std::endl;
447   if (!initrd_.empty()) {
448     o << "  if [ -e " << kInitrdDestPath << " ]; then" << std::endl;
449     o << "    initrd " << kInitrdDestPath << std::endl;
450     o << "  fi" << std::endl;
451   }
452   o << "}" << std::endl;
453 
454   return o.str();
455 }
456 
MultibootBinary(std::string multiboot)457 FuchsiaEspBuilder& FuchsiaEspBuilder::MultibootBinary(std::string multiboot) & {
458   multiboot_bin_ = std::move(multiboot);
459   return *this;
460 }
461 
Zedboot(std::string zedboot)462 FuchsiaEspBuilder& FuchsiaEspBuilder::Zedboot(std::string zedboot) & {
463   zedboot_ = std::move(zedboot);
464   return *this;
465 }
466 
Architecture(Arch arch)467 FuchsiaEspBuilder& FuchsiaEspBuilder::Architecture(Arch arch) & {
468   arch_ = arch;
469   return *this;
470 }
471 
Build() const472 bool FuchsiaEspBuilder::Build() const {
473   if (multiboot_bin_.empty()) {
474     LOG(ERROR) << "Multiboot esp path is required argument for FuchsiaEspBuilder";
475     return false;
476   }
477   if (zedboot_.empty()) {
478     LOG(ERROR) << "Zedboot esp path is required argument for FuchsiaEspBuilder";
479     return false;
480   }
481   if (!arch_) {
482     LOG(ERROR) << "Architecture is required argument for FuchsiaEspBuilder";
483     return false;
484   }
485 
486   auto builder = PrepareESP(image_path_, *arch_);
487 
488   const auto tmp_grub_config = image_path_ + ".grub.cfg";
489   const auto config_file = SharedFD::Creat(tmp_grub_config, 0644);
490   if (!config_file->IsOpen()) {
491     LOG(ERROR) << "Cannot create temporary grub config: " << tmp_grub_config;
492     return false;
493   }
494 
495   const auto dumped = DumpConfig();
496   if (WriteAll(config_file, dumped) != dumped.size()) {
497     LOG(ERROR) << "Failed to write grub config content to: " << tmp_grub_config;
498     return false;
499   }
500 
501   builder.Merge(AddGrubConfig(tmp_grub_config));
502   builder.File(multiboot_bin_, kMultibootBinDestPath, /*required*/ true);
503   builder.File(zedboot_, kZedbootDestPath, /*required*/ true);
504 
505   return builder.Build();
506 }
507 
DumpConfig() const508 std::string FuchsiaEspBuilder::DumpConfig() const {
509   std::ostringstream o;
510 
511   o << "set timeout=0" << std::endl
512     << "menuentry \"Fuchsia\" {" << std::endl
513     << "  insmod " << kMultibootModuleDestPathIA32 << std::endl
514     << "  multiboot " << kMultibootBinDestPath << std::endl
515     << "  module " << kZedbootDestPath << std::endl
516     << "}" << std::endl;
517 
518   return o.str();
519 }
520 
521 } // namespace cuttlefish
522