• 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 <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