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