• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 #include "host/commands/assemble_cvd/boot_image_utils.h"
18 #include "host/libs/config/cuttlefish_config.h"
19 
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include <fstream>
24 #include <regex>
25 #include <sstream>
26 
27 #include <android-base/logging.h>
28 #include <android-base/strings.h>
29 
30 #include "common/libs/utils/files.h"
31 #include "common/libs/utils/result.h"
32 #include "common/libs/utils/subprocess.h"
33 
34 const char TMP_EXTENSION[] = ".tmp";
35 const char CPIO_EXT[] = ".cpio";
36 const char TMP_RD_DIR[] = "stripped_ramdisk_dir";
37 const char STRIPPED_RD[] = "stripped_ramdisk";
38 const char CONCATENATED_VENDOR_RAMDISK[] = "concatenated_vendor_ramdisk";
39 namespace cuttlefish {
40 namespace {
ExtractValue(const std::string & dictionary,const std::string & key)41 std::string ExtractValue(const std::string& dictionary, const std::string& key) {
42   std::size_t index = dictionary.find(key);
43   if (index != std::string::npos) {
44     std::size_t end_index = dictionary.find('\n', index + key.length());
45     if (end_index != std::string::npos) {
46       return dictionary.substr(index + key.length(),
47           end_index - index - key.length());
48     }
49   }
50   return "";
51 }
52 
53 // Though it is just as fast to overwrite the existing boot images with the newly generated ones,
54 // the cuttlefish composite disk generator checks the age of each of the components and
55 // regenerates the disk outright IF any one of the components is younger/newer than the current
56 // composite disk. If this file overwrite occurs, that condition is fulfilled. This action then
57 // causes data in the userdata partition from previous boots to be lost (which is not expected by
58 // the user if they've been booting the same kernel/ramdisk combination repeatedly).
59 // Consequently, the file is checked for differences and ONLY overwritten if there is a diff.
DeleteTmpFileIfNotChanged(const std::string & tmp_file,const std::string & current_file)60 bool DeleteTmpFileIfNotChanged(const std::string& tmp_file, const std::string& current_file) {
61   if (!FileExists(current_file) ||
62       ReadFile(current_file) != ReadFile(tmp_file)) {
63     if (!RenameFile(tmp_file, current_file).ok()) {
64       LOG(ERROR) << "Unable to delete " << current_file;
65       return false;
66     }
67     LOG(DEBUG) << "Updated " << current_file;
68   } else {
69     LOG(DEBUG) << "Didn't update " << current_file;
70     RemoveFile(tmp_file);
71   }
72 
73   return true;
74 }
75 
RepackVendorRamdisk(const std::string & kernel_modules_ramdisk_path,const std::string & original_ramdisk_path,const std::string & new_ramdisk_path,const std::string & build_dir)76 void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
77                          const std::string& original_ramdisk_path,
78                          const std::string& new_ramdisk_path,
79                          const std::string& build_dir) {
80   int success = 0;
81   const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
82   UnpackRamdisk(original_ramdisk_path, ramdisk_stage_dir);
83 
84   success = execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
85   CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
86                       << "Exited with status " << success;
87 
88   const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
89   success = execute({"/bin/bash", "-c",
90                      HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
91                          " > " + stripped_ramdisk_path + CPIO_EXT});
92   CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
93                       << success;
94 
95   success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") +
96                      " -c -l -12 --favor-decSpeed " + stripped_ramdisk_path + CPIO_EXT + " > " +
97                      stripped_ramdisk_path});
98   CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
99 
100   // Concatenates the stripped ramdisk and input ramdisk and places the result at new_ramdisk_path
101   std::ofstream final_rd(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
102   std::ifstream ramdisk_a(stripped_ramdisk_path, std::ios_base::binary);
103   std::ifstream ramdisk_b(kernel_modules_ramdisk_path, std::ios_base::binary);
104   final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
105 }
106 
107 }  // namespace
108 
PackRamdisk(const std::string & ramdisk_stage_dir,const std::string & output_ramdisk)109 void PackRamdisk(const std::string& ramdisk_stage_dir,
110                  const std::string& output_ramdisk) {
111   int success = execute({"/bin/bash", "-c",
112                          HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
113                              " > " + output_ramdisk + CPIO_EXT});
114   CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
115                       << success;
116 
117   success = execute({"/bin/bash", "-c",
118                      HostBinaryPath("lz4") + " -c -l -12 --favor-decSpeed " +
119                          output_ramdisk + CPIO_EXT + " > " + output_ramdisk});
120   CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
121 }
122 
UnpackRamdisk(const std::string & original_ramdisk_path,const std::string & ramdisk_stage_dir)123 void UnpackRamdisk(const std::string& original_ramdisk_path,
124                    const std::string& ramdisk_stage_dir) {
125   int success =
126       execute({"/bin/bash", "-c",
127                HostBinaryPath("lz4") + " -c -d -l " + original_ramdisk_path +
128                    " > " + original_ramdisk_path + CPIO_EXT});
129   CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
130   const auto ret = EnsureDirectoryExists(ramdisk_stage_dir);
131   CHECK(ret.ok()) << ret.error().Message();
132 
133   success = execute(
134       {"/bin/bash", "-c",
135        "(cd " + ramdisk_stage_dir + " && while " + HostBinaryPath("toybox") +
136            " cpio -idu; do :; done) < " + original_ramdisk_path + CPIO_EXT});
137   CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
138                       << success;
139 }
140 
141 
UnpackBootImage(const std::string & boot_image_path,const std::string & unpack_dir)142 bool UnpackBootImage(const std::string& boot_image_path,
143                      const std::string& unpack_dir) {
144   auto unpack_path = HostBinaryPath("unpack_bootimg");
145   Command unpack_cmd(unpack_path);
146   unpack_cmd.AddParameter("--boot_img");
147   unpack_cmd.AddParameter(boot_image_path);
148   unpack_cmd.AddParameter("--out");
149   unpack_cmd.AddParameter(unpack_dir);
150 
151   auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
152   if (!output_file->IsOpen()) {
153     LOG(ERROR) << "Unable to create intermediate boot params file: "
154                << output_file->StrError();
155     return false;
156   }
157   unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
158 
159   int success = unpack_cmd.Start().Wait();
160   if (success != 0) {
161     LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
162                << success;
163     return false;
164   }
165   return true;
166 }
167 
UnpackVendorBootImageIfNotUnpacked(const std::string & vendor_boot_image_path,const std::string & unpack_dir)168 bool UnpackVendorBootImageIfNotUnpacked(
169     const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
170   // the vendor boot params file is created during the first unpack. If it's
171   // already there, a unpack has occurred and there's no need to repeat the
172   // process.
173   if (FileExists(unpack_dir + "/vendor_boot_params")) {
174     return true;
175   }
176 
177   auto unpack_path = HostBinaryPath("unpack_bootimg");
178   Command unpack_cmd(unpack_path);
179   unpack_cmd.AddParameter("--boot_img");
180   unpack_cmd.AddParameter(vendor_boot_image_path);
181   unpack_cmd.AddParameter("--out");
182   unpack_cmd.AddParameter(unpack_dir);
183   auto output_file = SharedFD::Creat(unpack_dir + "/vendor_boot_params", 0666);
184   if (!output_file->IsOpen()) {
185     LOG(ERROR) << "Unable to create intermediate vendor boot params file: "
186                << output_file->StrError();
187     return false;
188   }
189   unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
190   int success = unpack_cmd.Start().Wait();
191   if (success != 0) {
192     LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status " << success;
193     return false;
194   }
195 
196   // Concatenates all vendor ramdisk into one single ramdisk.
197   Command concat_cmd("/bin/bash");
198   concat_cmd.AddParameter("-c");
199   concat_cmd.AddParameter("cat " + unpack_dir + "/vendor_ramdisk*");
200   auto concat_file =
201       SharedFD::Creat(unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK, 0666);
202   if (!concat_file->IsOpen()) {
203     LOG(ERROR) << "Unable to create concatenated vendor ramdisk file: "
204                << concat_file->StrError();
205     return false;
206   }
207   concat_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, concat_file);
208   success = concat_cmd.Start().Wait();
209   if (success != 0) {
210     LOG(ERROR) << "Unable to run cat. Exited with status " << success;
211     return false;
212   }
213   return true;
214 }
215 
RepackBootImage(const std::string & new_kernel_path,const std::string & boot_image_path,const std::string & new_boot_image_path,const std::string & build_dir)216 bool RepackBootImage(const std::string& new_kernel_path,
217                      const std::string& boot_image_path,
218                      const std::string& new_boot_image_path,
219                      const std::string& build_dir) {
220   if (UnpackBootImage(boot_image_path, build_dir) == false) {
221     return false;
222   }
223 
224   std::string boot_params = ReadFile(build_dir + "/boot_params");
225   auto kernel_cmdline = ExtractValue(boot_params, "command line args: ");
226   LOG(DEBUG) << "Cmdline from boot image is " << kernel_cmdline;
227 
228   auto tmp_boot_image_path = new_boot_image_path + TMP_EXTENSION;
229   auto repack_path = HostBinaryPath("mkbootimg");
230   Command repack_cmd(repack_path);
231   repack_cmd.AddParameter("--kernel");
232   repack_cmd.AddParameter(new_kernel_path);
233   repack_cmd.AddParameter("--ramdisk");
234   repack_cmd.AddParameter(build_dir + "/ramdisk");
235   repack_cmd.AddParameter("--header_version");
236   repack_cmd.AddParameter("4");
237   repack_cmd.AddParameter("--cmdline");
238   repack_cmd.AddParameter(kernel_cmdline);
239   repack_cmd.AddParameter("-o");
240   repack_cmd.AddParameter(tmp_boot_image_path);
241   int success = repack_cmd.Start().Wait();
242   if (success != 0) {
243     LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
244     return false;
245   }
246 
247   auto avbtool_path = HostBinaryPath("avbtool");
248   Command avb_cmd(avbtool_path);
249   avb_cmd.AddParameter("add_hash_footer");
250   avb_cmd.AddParameter("--image");
251   avb_cmd.AddParameter(tmp_boot_image_path);
252   avb_cmd.AddParameter("--partition_size");
253   avb_cmd.AddParameter(FileSize(boot_image_path));
254   avb_cmd.AddParameter("--partition_name");
255   avb_cmd.AddParameter("boot");
256   success = avb_cmd.Start().Wait();
257   if (success != 0) {
258     LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
259     return false;
260   }
261 
262   return DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path);
263 }
264 
RepackVendorBootImage(const std::string & new_ramdisk,const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,bool bootconfig_supported)265 bool RepackVendorBootImage(const std::string& new_ramdisk,
266                            const std::string& vendor_boot_image_path,
267                            const std::string& new_vendor_boot_image_path,
268                            const std::string& unpack_dir,
269                            bool bootconfig_supported) {
270   if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
271       false) {
272     return false;
273   }
274 
275   std::string ramdisk_path;
276   if (new_ramdisk.size()) {
277     ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
278     if (!FileExists(ramdisk_path)) {
279       RepackVendorRamdisk(new_ramdisk,
280                           unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
281                           ramdisk_path, unpack_dir);
282     }
283   } else {
284     ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
285   }
286 
287   std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
288   LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
289              << bootconfig;
290   std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
291   auto kernel_cmdline =
292       ExtractValue(vendor_boot_params, "vendor command line args: ") +
293       (bootconfig_supported
294            ? ""
295            : " " + android::base::StringReplace(bootconfig, "\n", " ", true));
296   if (!bootconfig_supported) {
297     // TODO(b/182417593): Until we pass the module parameters through
298     // modules.options, we pass them through bootconfig using
299     // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
300     // rename them back to the old cmdline version
301     kernel_cmdline = android::base::StringReplace(
302         kernel_cmdline, " kernel.", " ", true);
303   }
304   LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
305 
306   auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
307   auto repack_path = HostBinaryPath("mkbootimg");
308   Command repack_cmd(repack_path);
309   repack_cmd.AddParameter("--vendor_ramdisk");
310   repack_cmd.AddParameter(ramdisk_path);
311   repack_cmd.AddParameter("--header_version");
312   repack_cmd.AddParameter("4");
313   repack_cmd.AddParameter("--vendor_cmdline");
314   repack_cmd.AddParameter(kernel_cmdline);
315   repack_cmd.AddParameter("--vendor_boot");
316   repack_cmd.AddParameter(tmp_vendor_boot_image_path);
317   repack_cmd.AddParameter("--dtb");
318   repack_cmd.AddParameter(unpack_dir + "/dtb");
319   if (bootconfig_supported) {
320     repack_cmd.AddParameter("--vendor_bootconfig");
321     repack_cmd.AddParameter(unpack_dir + "/bootconfig");
322   }
323 
324   int success = repack_cmd.Start().Wait();
325   if (success != 0) {
326     LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
327     return false;
328   }
329 
330   auto avbtool_path = HostBinaryPath("avbtool");
331   Command avb_cmd(avbtool_path);
332   avb_cmd.AddParameter("add_hash_footer");
333   avb_cmd.AddParameter("--image");
334   avb_cmd.AddParameter(tmp_vendor_boot_image_path);
335   avb_cmd.AddParameter("--partition_size");
336   avb_cmd.AddParameter(FileSize(vendor_boot_image_path));
337   avb_cmd.AddParameter("--partition_name");
338   avb_cmd.AddParameter("vendor_boot");
339   success = avb_cmd.Start().Wait();
340   if (success != 0) {
341     LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
342     return false;
343   }
344 
345   return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
346 }
347 
RepackVendorBootImageWithEmptyRamdisk(const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,bool bootconfig_supported)348 bool RepackVendorBootImageWithEmptyRamdisk(
349     const std::string& vendor_boot_image_path,
350     const std::string& new_vendor_boot_image_path,
351     const std::string& unpack_dir, bool bootconfig_supported) {
352   auto empty_ramdisk_file =
353       SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
354   return RepackVendorBootImage(
355       unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
356       new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
357 }
358 
RepackGem5BootImage(const std::string & initrd_path,const std::string & bootconfig_path,const std::string & unpack_dir,const std::string & input_ramdisk_path)359 void RepackGem5BootImage(const std::string& initrd_path,
360                          const std::string& bootconfig_path,
361                          const std::string& unpack_dir,
362                          const std::string& input_ramdisk_path) {
363   // Simulate per-instance what the bootloader would usually do
364   // Since on other devices this runs every time, just do it here every time
365   std::ofstream final_rd(initrd_path,
366                          std::ios_base::binary | std::ios_base::trunc);
367 
368   std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
369                              std::ios_base::binary);
370   std::string new_ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
371   // Test to make sure new ramdisk hasn't already been repacked if input ramdisk is provided
372   if (FileExists(input_ramdisk_path) && !FileExists(new_ramdisk_path)) {
373     RepackVendorRamdisk(input_ramdisk_path,
374                         unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
375                         new_ramdisk_path, unpack_dir);
376   }
377   std::ifstream vendor_boot_ramdisk(FileExists(new_ramdisk_path) ? new_ramdisk_path : unpack_dir +
378                                     "/concatenated_vendor_ramdisk",
379                                     std::ios_base::binary);
380 
381   std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
382                                        std::ios_base::binary |
383                                        std::ios_base::ate);
384 
385   auto vb_size = vendor_boot_bootconfig.tellg();
386   vendor_boot_bootconfig.seekg(0);
387 
388   std::ifstream persistent_bootconfig(bootconfig_path,
389                                       std::ios_base::binary |
390                                       std::ios_base::ate);
391 
392   auto pb_size = persistent_bootconfig.tellg();
393   persistent_bootconfig.seekg(0);
394 
395   // Build the bootconfig string, trim it, and write the length, checksum
396   // and trailer bytes
397 
398   std::string bootconfig =
399     "androidboot.slot_suffix=_a\n"
400     "androidboot.force_normal_boot=1\n"
401     "androidboot.verifiedbootstate=orange\n";
402   auto bootconfig_size = bootconfig.size();
403   bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
404   vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
405   persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
406   // Trim the block size padding from the persistent bootconfig
407   bootconfig.erase(bootconfig.find_last_not_of('\0'));
408 
409   // Write out the ramdisks and bootconfig blocks
410   final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
411            << bootconfig;
412 
413   // Append bootconfig length
414   bootconfig_size = bootconfig.size();
415   final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
416                  sizeof(uint32_t));
417 
418   // Append bootconfig checksum
419   uint32_t bootconfig_csum = 0;
420   for (auto i = 0; i < bootconfig_size; i++) {
421     bootconfig_csum += bootconfig[i];
422   }
423   final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
424                  sizeof(uint32_t));
425 
426   // Append bootconfig trailer
427   final_rd << "#BOOTCONFIG\n";
428   final_rd.close();
429 }
430 
ReadAndroidVersionFromBootImage(const std::string & boot_image_path)431 Result<std::string> ReadAndroidVersionFromBootImage(
432     const std::string& boot_image_path) {
433   // temp dir path length is chosen to be larger than sun_path_length (108)
434   char tmp_dir[200];
435   sprintf(tmp_dir, "%s/XXXXXX", StringFromEnv("TEMP", "/tmp").c_str());
436   char* unpack_dir = mkdtemp(tmp_dir);
437   if (!unpack_dir) {
438     return CF_ERR("boot image unpack dir could not be created");
439   }
440   bool unpack_status = UnpackBootImage(boot_image_path, unpack_dir);
441   if (!unpack_status) {
442     RecursivelyRemoveDirectory(unpack_dir);
443     return CF_ERR("\"" + boot_image_path + "\" boot image unpack into \"" +
444                   unpack_dir + "\" failed");
445   }
446 
447   // dirty hack to read out boot params
448   size_t dir_path_len = strlen(tmp_dir);
449   std::string boot_params = ReadFile(strcat(unpack_dir, "/boot_params"));
450   unpack_dir[dir_path_len] = '\0';
451 
452   RecursivelyRemoveDirectory(unpack_dir);
453   std::string os_version = ExtractValue(boot_params, "os version: ");
454   CF_EXPECT(os_version != "", "Could not extract os version from \"" + boot_image_path + "\"");
455   std::regex re("[1-9][0-9]*.[0-9]+.[0-9]+");
456   CF_EXPECT(std::regex_match(os_version, re), "Version string is not a valid version \"" + os_version + "\"");
457   return os_version;
458 }
459 } // namespace cuttlefish
460