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