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 <sstream>
17
18 #include "host/libs/config/esp.h"
19 #include "common/libs/fs/shared_buf.h"
20 #include "common/libs/utils/subprocess.h"
21 #include "common/libs/utils/files.h"
22 #include "host/libs/config/cuttlefish_config.h"
23
24 namespace cuttlefish {
25
NewfsMsdos(const std::string & data_image,int data_image_mb,int offset_num_mb)26 bool NewfsMsdos(const std::string& data_image, int data_image_mb,
27 int offset_num_mb) {
28 off_t image_size_bytes = static_cast<off_t>(data_image_mb) << 20;
29 off_t offset_size_bytes = static_cast<off_t>(offset_num_mb) << 20;
30 image_size_bytes -= offset_size_bytes;
31 off_t image_size_sectors = image_size_bytes / 512;
32 auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
33 return execute({newfs_msdos_path,
34 "-F",
35 "32",
36 "-m",
37 "0xf8",
38 "-o",
39 "0",
40 "-c",
41 "8",
42 "-h",
43 "255",
44 "-u",
45 "63",
46 "-S",
47 "512",
48 "-s",
49 std::to_string(image_size_sectors),
50 "-C",
51 std::to_string(data_image_mb) + "M",
52 "-@",
53 std::to_string(offset_size_bytes),
54 data_image}) == 0;
55 }
56
CanGenerateEsp(Arch arch)57 bool CanGenerateEsp(Arch arch) {
58 switch (arch) {
59 case Arch::Arm:
60 case Arch::Arm64:
61 case Arch::RiscV64:
62 // TODO(b/260960328) : Migrate openwrt image for arm64 into
63 // APBootFlow::Grub.
64 return false;
65 case Arch::X86:
66 case Arch::X86_64: {
67 const auto x86_modules = std::string(kGrubModulesPath) + std::string(kGrubModulesX86Name);
68 const auto modules_presented = all_of(kGrubModulesX86.begin(), kGrubModulesX86.end(),
69 [&](const std::string& m) {
70 return FileExists(x86_modules + m);
71 });
72 if (modules_presented) return true;
73
74 const auto monolith_presented = FileExists(kBootSrcPathIA32);
75 return monolith_presented;
76 }
77 }
78
79 return false;
80 }
81
MsdosMakeDirectories(const std::string & image_path,const std::vector<std::string> & directories)82 bool MsdosMakeDirectories(const std::string& image_path,
83 const std::vector<std::string>& directories) {
84 auto mmd = HostBinaryPath("mmd");
85 std::vector<std::string> command {mmd, "-i", image_path};
86 command.insert(command.end(), directories.begin(), directories.end());
87
88 const auto success = execute(command);
89 if (success != 0) {
90 return false;
91 }
92 return true;
93 }
94
CopyToMsdos(const std::string & image,const std::string & path,const std::string & destination)95 bool CopyToMsdos(const std::string& image, const std::string& path,
96 const std::string& destination) {
97 const auto mcopy = HostBinaryPath("mcopy");
98 const auto success = execute({mcopy, "-o", "-i", image, "-s", path, destination});
99 if (success != 0) {
100 return false;
101 }
102 return true;
103 }
104
GrubMakeImage(const std::string & prefix,const std::string & format,const std::string & directory,const std::string & output,std::vector<std::string> modules)105 bool GrubMakeImage(const std::string& prefix, const std::string& format,
106 const std::string& directory, const std::string& output,
107 std::vector<std::string> modules) {
108 std::vector<std::string> command = {"grub-mkimage", "--prefix", prefix,
109 "--format", format, "--directory", directory,
110 "--output", output};
111 std::move(modules.begin(), modules.end(), std::back_inserter(command));
112
113 const auto success = execute(command);
114 return success == 0;
115 }
116
117 class EspBuilder final {
118 public:
EspBuilder()119 EspBuilder() {}
EspBuilder(std::string image_path)120 EspBuilder(std::string image_path): image_path_(std::move(image_path)) {}
121
File(std::string from,std::string to,bool required)122 EspBuilder& File(std::string from, std::string to, bool required) & {
123 files_.push_back(FileToAdd {std::move(from), std::move(to), required});
124 return *this;
125 }
126
File(std::string from,std::string to)127 EspBuilder& File(std::string from, std::string to) & {
128 return File(std::move(from), std::move(to), false);
129 }
130
Directory(std::string path)131 EspBuilder& Directory(std::string path) & {
132 directories_.push_back(std::move(path));
133 return *this;
134 }
135
Merge(EspBuilder builder)136 EspBuilder& Merge(EspBuilder builder) & {
137 std::move(builder.directories_.begin(), builder.directories_.end(),
138 std::back_inserter(directories_));
139 std::move(builder.files_.begin(), builder.files_.end(),
140 std::back_inserter(files_));
141 return *this;
142 }
143
Build()144 bool Build() {
145 if (image_path_.empty()) {
146 LOG(ERROR) << "Image path is required to build ESP. Empty constructor is intended to "
147 << "be used only for the merge functionality";
148 return false;
149 }
150
151 // newfs_msdos won't make a partition smaller than 257 mb
152 // this should be enough for anybody..
153 const auto tmp_esp_image = image_path_ + ".tmp";
154 if (!NewfsMsdos(tmp_esp_image, 257 /* mb */, 0 /* mb (offset) */)) {
155 LOG(ERROR) << "Failed to create filesystem for " << tmp_esp_image;
156 return false;
157 }
158
159 if (!MsdosMakeDirectories(tmp_esp_image, directories_)) {
160 LOG(ERROR) << "Failed to create directories in " << tmp_esp_image;
161 return false;
162 }
163
164 for (const FileToAdd& file : files_) {
165 if (!FileExists(file.from)) {
166 if (file.required) {
167 LOG(ERROR) << "Failed to copy " << file.from << " to " << tmp_esp_image
168 << ": File does not exist";
169 return false;
170 }
171 continue;
172 }
173
174 if (!CopyToMsdos(tmp_esp_image, file.from, "::" + file.to)) {
175 LOG(ERROR) << "Failed to copy " << file.from << " to " << tmp_esp_image
176 << ": mcopy execution failed";
177 return false;
178 }
179 }
180
181 if (!RenameFile(tmp_esp_image, image_path_).ok()) {
182 LOG(ERROR) << "Renaming " << tmp_esp_image << " to "
183 << image_path_ << " failed";
184 return false;
185 }
186
187 return true;
188 }
189
190 private:
191 const std::string image_path_;
192
193 struct FileToAdd {
194 std::string from;
195 std::string to;
196 bool required;
197 };
198 std::vector<std::string> directories_;
199 std::vector<FileToAdd> files_;
200 };
201
PrepareESP(const std::string & image_path,Arch arch)202 EspBuilder PrepareESP(const std::string& image_path, Arch arch) {
203 auto builder = EspBuilder(image_path);
204 builder.Directory("EFI")
205 .Directory("EFI/BOOT")
206 .Directory("EFI/modules");
207
208 const auto efi_path = image_path + ".efi";
209 switch (arch) {
210 case Arch::Arm:
211 case Arch::Arm64:
212 builder.File(kBootSrcPathAA64, kBootDestPathAA64, /* required */ true);
213 // Not required for arm64 due missing it in deb package, so fuchsia is
214 // not supported for it.
215 builder.File(kMultibootModuleSrcPathAA64, kMultibootModuleDestPathAA64,
216 /* required */ false);
217 break;
218 case Arch::RiscV64:
219 // FIXME: Implement
220 break;
221 case Arch::X86:
222 case Arch::X86_64: {
223 const auto x86_modules = std::string(kGrubModulesPath) + std::string(kGrubModulesX86Name);
224
225 if (GrubMakeImage(kGrubConfigDestDirectoryPath, kGrubModulesX86Name,
226 x86_modules, efi_path, kGrubModulesX86)) {
227 LOG(INFO) << "Loading grub_mkimage generated EFI binary";
228 builder.File(efi_path, kBootDestPathIA32, /* required */ true);
229 } else {
230 LOG(INFO) << "Loading prebuilt monolith EFI binary";
231 builder.File(kBootSrcPathIA32, kBootDestPathIA32, /* required */ true);
232 builder.File(kMultibootModuleSrcPathIA32, kMultibootModuleDestPathIA32,
233 /* required */ true);
234 }
235 break;
236 }
237 }
238
239 return std::move(builder);
240 }
241
242 // TODO(b/260338443, b/260337906) remove ubuntu and debian variations
243 // after migrating to grub-mkimage or adding grub binaries as a prebuilt
AddGrubConfig(const std::string & config)244 EspBuilder AddGrubConfig(const std::string& config) {
245 auto builder = EspBuilder();
246
247 builder.Directory("boot")
248 .Directory("EFI/debian")
249 .Directory("EFI/ubuntu")
250 .Directory("boot/grub");
251
252 builder.File(config, kGrubDebianConfigDestPath, /*required*/ true)
253 .File(config, kGrubUbuntuConfigDestPath, /*required*/ true)
254 .File(config, kGrubConfigDestPath, /*required*/ true);
255
256 return builder;
257 }
258
Argument(std::string key,std::string value)259 LinuxEspBuilder& LinuxEspBuilder::Argument(std::string key, std::string value) & {
260 arguments_.push_back({std::move(key), std::move(value)});
261 return *this;
262 }
263
Argument(std::string value)264 LinuxEspBuilder& LinuxEspBuilder::Argument(std::string value) & {
265 single_arguments_.push_back(std::move(value));
266 return *this;
267 }
268
Root(std::string root)269 LinuxEspBuilder& LinuxEspBuilder::Root(std::string root) & {
270 root_ = std::move(root);
271 return *this;
272 }
273
Kernel(std::string kernel)274 LinuxEspBuilder& LinuxEspBuilder::Kernel(std::string kernel) & {
275 kernel_ = std::move(kernel);
276 return *this;
277 }
278
Initrd(std::string initrd)279 LinuxEspBuilder& LinuxEspBuilder::Initrd(std::string initrd) & {
280 initrd_ = std::move(initrd);
281 return *this;
282 }
283
Architecture(Arch arch)284 LinuxEspBuilder& LinuxEspBuilder::Architecture(Arch arch) & {
285 arch_ = arch;
286 return *this;
287 }
288
Build() const289 bool LinuxEspBuilder::Build() const {
290 if (root_.empty()) {
291 LOG(ERROR) << "Root is required argument for LinuxEspBuilder";
292 return false;
293 }
294 if (kernel_.empty()) {
295 LOG(ERROR) << "Kernel esp path is required argument for LinuxEspBuilder";
296 return false;
297 }
298 if (!arch_) {
299 LOG(ERROR) << "Architecture is required argument for LinuxEspBuilder";
300 return false;
301 }
302
303 auto builder = PrepareESP(image_path_, *arch_);
304
305 const auto tmp_grub_config = image_path_ + ".grub.cfg";
306 const auto config_file = SharedFD::Creat(tmp_grub_config, 0644);
307 if (!config_file->IsOpen()) {
308 LOG(ERROR) << "Cannot create temporary grub config: " << tmp_grub_config;
309 return false;
310 }
311
312 const auto dumped = DumpConfig();
313 if (WriteAll(config_file, dumped) != dumped.size()) {
314 LOG(ERROR) << "Failed to write grub config content to: " << tmp_grub_config;
315 return false;
316 }
317
318 builder.Merge(AddGrubConfig(tmp_grub_config));
319 builder.File(kernel_, kKernelDestPath, /*required*/ true);
320 if (!initrd_.empty()) {
321 builder.File(initrd_, kInitrdDestPath, /*required*/ true);
322 }
323
324 return builder.Build();
325 }
326
DumpConfig() const327 std::string LinuxEspBuilder::DumpConfig() const {
328 std::ostringstream o;
329
330 o << "set timeout=0" << std::endl
331 << "menuentry \"Linux\" {" << std::endl
332 << " linux " << kKernelDestPath << " ";
333
334 for (int i = 0; i < arguments_.size(); i++) {
335 o << arguments_[i].first << "=" << arguments_[i].second << " ";
336 }
337 for (int i = 0; i < single_arguments_.size(); i++) {
338 o << single_arguments_[i] << " ";
339 }
340 o << "root=" << root_ << std::endl;
341 if (!initrd_.empty()) {
342 o << " if [ -e " << kInitrdDestPath << " ]; then" << std::endl;
343 o << " initrd " << kInitrdDestPath << std::endl;
344 o << " fi" << std::endl;
345 }
346 o << "}" << std::endl;
347
348 return o.str();
349 }
350
MultibootBinary(std::string multiboot)351 FuchsiaEspBuilder& FuchsiaEspBuilder::MultibootBinary(std::string multiboot) & {
352 multiboot_bin_ = std::move(multiboot);
353 return *this;
354 }
355
Zedboot(std::string zedboot)356 FuchsiaEspBuilder& FuchsiaEspBuilder::Zedboot(std::string zedboot) & {
357 zedboot_ = std::move(zedboot);
358 return *this;
359 }
360
Architecture(Arch arch)361 FuchsiaEspBuilder& FuchsiaEspBuilder::Architecture(Arch arch) & {
362 arch_ = arch;
363 return *this;
364 }
365
Build() const366 bool FuchsiaEspBuilder::Build() const {
367 if (multiboot_bin_.empty()) {
368 LOG(ERROR) << "Multiboot esp path is required argument for FuchsiaEspBuilder";
369 return false;
370 }
371 if (zedboot_.empty()) {
372 LOG(ERROR) << "Zedboot esp path is required argument for FuchsiaEspBuilder";
373 return false;
374 }
375 if (!arch_) {
376 LOG(ERROR) << "Architecture is required argument for FuchsiaEspBuilder";
377 return false;
378 }
379
380 auto builder = PrepareESP(image_path_, *arch_);
381
382 const auto tmp_grub_config = image_path_ + ".grub.cfg";
383 const auto config_file = SharedFD::Creat(tmp_grub_config, 0644);
384 if (!config_file->IsOpen()) {
385 LOG(ERROR) << "Cannot create temporary grub config: " << tmp_grub_config;
386 return false;
387 }
388
389 const auto dumped = DumpConfig();
390 if (WriteAll(config_file, dumped) != dumped.size()) {
391 LOG(ERROR) << "Failed to write grub config content to: " << tmp_grub_config;
392 return false;
393 }
394
395 builder.Merge(AddGrubConfig(tmp_grub_config));
396 builder.File(multiboot_bin_, kMultibootBinDestPath, /*required*/ true);
397 builder.File(zedboot_, kZedbootDestPath, /*required*/ true);
398
399 return builder.Build();
400 }
401
DumpConfig() const402 std::string FuchsiaEspBuilder::DumpConfig() const {
403 std::ostringstream o;
404
405 o << "set timeout=0" << std::endl
406 << "menuentry \"Fuchsia\" {" << std::endl
407 << " insmod " << kMultibootModuleDestPathIA32 << std::endl
408 << " multiboot " << kMultibootBinDestPath << std::endl
409 << " module " << kZedbootDestPath << std::endl
410 << "}" << std::endl;
411
412 return o.str();
413 }
414
415 } // namespace cuttlefish
416