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