• 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 
19 #include <string.h>
20 #include <unistd.h>
21 
22 #include <fstream>
23 #include <memory>
24 #include <regex>
25 #include <string>
26 
27 #include <android-base/logging.h>
28 #include <android-base/strings.h>
29 
30 #include "android-base/scopeguard.h"
31 #include "common/libs/fs/shared_fd.h"
32 #include "common/libs/utils/files.h"
33 #include "common/libs/utils/result.h"
34 #include "common/libs/utils/subprocess.h"
35 #include "host/libs/avb/avb.cpp"
36 #include "host/libs/config/config_utils.h"
37 #include "host/libs/config/cuttlefish_config.h"
38 #include "host/libs/config/known_paths.h"
39 
40 namespace cuttlefish {
41 namespace {
42 
43 constexpr char TMP_EXTENSION[] = ".tmp";
44 constexpr char kCpioExt[] = ".cpio";
45 constexpr char TMP_RD_DIR[] = "stripped_ramdisk_dir";
46 constexpr char STRIPPED_RD[] = "stripped_ramdisk";
47 constexpr char kConcatenatedVendorRamdisk[] = "concatenated_vendor_ramdisk";
48 
RunMkBootFs(const std::string & input_dir,const std::string & output)49 void RunMkBootFs(const std::string& input_dir, const std::string& output) {
50   SharedFD output_fd = SharedFD::Open(output, O_CREAT | O_RDWR | O_TRUNC, 0644);
51   CHECK(output_fd->IsOpen()) << output_fd->StrError();
52 
53   int success = Command(HostBinaryPath("mkbootfs"))
54                     .AddParameter(input_dir)
55                     .RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_fd)
56                     .Start()
57                     .Wait();
58   CHECK_EQ(success, 0) << "`mkbootfs` failed.";
59 }
60 
RunLz4(const std::string & input,const std::string & output)61 void RunLz4(const std::string& input, const std::string& output) {
62   SharedFD output_fd = SharedFD::Open(output, O_CREAT | O_RDWR | O_TRUNC, 0644);
63   CHECK(output_fd->IsOpen()) << output_fd->StrError();
64   int success = Command(HostBinaryPath("lz4"))
65                     .AddParameter("-c")
66                     .AddParameter("-l")
67                     .AddParameter("-12")
68                     .AddParameter("--favor-decSpeed")
69                     .AddParameter(input)
70                     .RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_fd)
71                     .Start()
72                     .Wait();
73   CHECK_EQ(success, 0) << "`lz4` failed to transform '" << input << "' to '"
74                        << output << "'";
75 }
76 
ExtractValue(const std::string & dictionary,const std::string & key)77 std::string ExtractValue(const std::string& dictionary, const std::string& key) {
78   std::size_t index = dictionary.find(key);
79   if (index != std::string::npos) {
80     std::size_t end_index = dictionary.find('\n', index + key.length());
81     if (end_index != std::string::npos) {
82       return dictionary.substr(index + key.length(),
83           end_index - index - key.length());
84     }
85   }
86   return "";
87 }
88 
89 // Though it is just as fast to overwrite the existing boot images with the newly generated ones,
90 // the cuttlefish composite disk generator checks the age of each of the components and
91 // regenerates the disk outright IF any one of the components is younger/newer than the current
92 // composite disk. If this file overwrite occurs, that condition is fulfilled. This action then
93 // causes data in the userdata partition from previous boots to be lost (which is not expected by
94 // the user if they've been booting the same kernel/ramdisk combination repeatedly).
95 // 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)96 bool DeleteTmpFileIfNotChanged(const std::string& tmp_file, const std::string& current_file) {
97   if (!FileExists(current_file) ||
98       ReadFile(current_file) != ReadFile(tmp_file)) {
99     if (!RenameFile(tmp_file, current_file).ok()) {
100       LOG(ERROR) << "Unable to delete " << current_file;
101       return false;
102     }
103     LOG(DEBUG) << "Updated " << current_file;
104   } else {
105     LOG(DEBUG) << "Didn't update " << current_file;
106     RemoveFile(tmp_file);
107   }
108 
109   return true;
110 }
111 
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)112 void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
113                          const std::string& original_ramdisk_path,
114                          const std::string& new_ramdisk_path,
115                          const std::string& build_dir) {
116   int success = 0;
117   const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
118   UnpackRamdisk(original_ramdisk_path, ramdisk_stage_dir);
119 
120   success = Execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
121   CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
122                       << "Exited with status " << success;
123 
124   const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
125 
126   PackRamdisk(ramdisk_stage_dir, stripped_ramdisk_path);
127 
128   // Concatenates the stripped ramdisk and input ramdisk and places the result at new_ramdisk_path
129   std::ofstream final_rd(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
130   std::ifstream ramdisk_a(stripped_ramdisk_path, std::ios_base::binary);
131   std::ifstream ramdisk_b(kernel_modules_ramdisk_path, std::ios_base::binary);
132   final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
133 }
134 
IsCpioArchive(const std::string & path)135 bool IsCpioArchive(const std::string& path) {
136   static constexpr std::string_view CPIO_MAGIC = "070701";
137   auto fd = SharedFD::Open(path, O_RDONLY);
138   std::array<char, CPIO_MAGIC.size()> buf{};
139   if (fd->Read(buf.data(), buf.size()) != CPIO_MAGIC.size()) {
140     return false;
141   }
142   return memcmp(buf.data(), CPIO_MAGIC.data(), CPIO_MAGIC.size()) == 0;
143 }
144 
145 }  // namespace
146 
PackRamdisk(const std::string & ramdisk_stage_dir,const std::string & output_ramdisk)147 void PackRamdisk(const std::string& ramdisk_stage_dir,
148                  const std::string& output_ramdisk) {
149   RunMkBootFs(ramdisk_stage_dir, output_ramdisk + kCpioExt);
150   RunLz4(output_ramdisk + kCpioExt, output_ramdisk);
151 }
152 
UnpackRamdisk(const std::string & original_ramdisk_path,const std::string & ramdisk_stage_dir)153 void UnpackRamdisk(const std::string& original_ramdisk_path,
154                    const std::string& ramdisk_stage_dir) {
155   int success = 0;
156   if (IsCpioArchive(original_ramdisk_path)) {
157     CHECK(Copy(original_ramdisk_path, original_ramdisk_path + kCpioExt))
158         << "failed to copy " << original_ramdisk_path << " to "
159         << original_ramdisk_path + kCpioExt;
160   } else {
161     SharedFD output_fd = SharedFD::Open(original_ramdisk_path + kCpioExt,
162                                         O_CREAT | O_RDWR | O_TRUNC, 0644);
163     CHECK(output_fd->IsOpen()) << output_fd->StrError();
164 
165     success = Command(HostBinaryPath("lz4"))
166                   .AddParameter("-c")
167                   .AddParameter("-d")
168                   .AddParameter("-l")
169                   .AddParameter(original_ramdisk_path)
170                   .RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_fd)
171                   .Start()
172                   .Wait();
173     CHECK_EQ(success, 0) << "Unable to run lz4 on file '"
174                          << original_ramdisk_path << "'.";
175   }
176   const auto ret = EnsureDirectoryExists(ramdisk_stage_dir);
177   CHECK(ret.ok()) << ret.error().FormatForEnv();
178 
179   SharedFD input = SharedFD::Open(original_ramdisk_path + kCpioExt, O_RDONLY);
180   int cpio_status;
181   do {
182     LOG(ERROR) << "Running";
183     cpio_status = Command(HostBinaryPath("toybox"))
184                       .AddParameter("cpio")
185                       .AddParameter("-idu")
186                       .SetWorkingDirectory(ramdisk_stage_dir)
187                       .RedirectStdIO(Subprocess::StdIOChannel::kStdIn, input)
188                       .Start()
189                       .Wait();
190   } while (cpio_status == 0);
191 }
192 
GetAvbMetadataFromBootImage(const std::string & boot_image_path,const std::string & unpack_dir)193 bool GetAvbMetadataFromBootImage(const std::string& boot_image_path,
194                                  const std::string& unpack_dir) {
195   std::unique_ptr<Avb> avbtool = GetDefaultAvb();
196   Result<void> result =
197       avbtool->WriteInfoImage(boot_image_path, unpack_dir + "/boot_params");
198   if (!result.ok()) {
199     LOG(ERROR) << result.error().Trace();
200     return false;
201   }
202   return true;
203 }
204 
UnpackBootImage(const std::string & boot_image_path,const std::string & unpack_dir)205 Result<void> UnpackBootImage(const std::string& boot_image_path,
206                              const std::string& unpack_dir) {
207   SharedFD output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
208   CF_EXPECTF(output_file->IsOpen(),
209              "Unable to create intermediate boot params file: '{}'",
210              output_file->StrError());
211 
212   Command unpack_cmd =
213       Command(HostBinaryPath("unpack_bootimg"))
214           .AddParameter("--boot_img")
215           .AddParameter(boot_image_path)
216           .AddParameter("--out")
217           .AddParameter(unpack_dir)
218           .RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
219 
220   CF_EXPECT_EQ(unpack_cmd.Start().Wait(), 0, "Unable to run unpack_bootimg.");
221 
222   return {};
223 }
224 
UnpackVendorBootImageIfNotUnpacked(const std::string & vendor_boot_image_path,const std::string & unpack_dir)225 bool UnpackVendorBootImageIfNotUnpacked(
226     const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
227   // the vendor boot params file is created during the first unpack. If it's
228   // already there, a unpack has occurred and there's no need to repeat the
229   // process.
230   if (FileExists(unpack_dir + "/vendor_boot_params")) {
231     return true;
232   }
233 
234   auto unpack_path = HostBinaryPath("unpack_bootimg");
235   Command unpack_cmd(unpack_path);
236   unpack_cmd.AddParameter("--boot_img");
237   unpack_cmd.AddParameter(vendor_boot_image_path);
238   unpack_cmd.AddParameter("--out");
239   unpack_cmd.AddParameter(unpack_dir);
240   auto output_file = SharedFD::Creat(unpack_dir + "/vendor_boot_params", 0666);
241   if (!output_file->IsOpen()) {
242     LOG(ERROR) << "Unable to create intermediate vendor boot params file: "
243                << output_file->StrError();
244     return false;
245   }
246   unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
247   int success = unpack_cmd.Start().Wait();
248   if (success != 0) {
249     LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status " << success;
250     return false;
251   }
252 
253   // Concatenates all vendor ramdisk into one single ramdisk.
254   std::string concat_file_path = unpack_dir + "/" + kConcatenatedVendorRamdisk;
255   SharedFD concat_file = SharedFD::Creat(concat_file_path, 0666);
256   if (!concat_file->IsOpen()) {
257     LOG(ERROR) << "Unable to create concatenated vendor ramdisk file: "
258                << concat_file->StrError();
259     return false;
260   }
261 
262   Result<std::vector<std::string>> unpack_files = DirectoryContents(unpack_dir);
263   if (!unpack_files.ok()) {
264     LOG(ERROR) << "No unpacked files: " << unpack_files.error().FormatForEnv();
265     return false;
266   }
267   for (const std::string& unpacked : *unpack_files) {
268     LOG(ERROR) << "acs: " << unpacked;
269     if (!android::base::StartsWith(unpacked, "vendor_ramdisk")) {
270       continue;
271     }
272     std::string input_path = unpack_dir + "/" + unpacked;
273     SharedFD input = SharedFD::Open(input_path, O_RDONLY);
274     if (!input->IsOpen()) {
275       LOG(ERROR) << "Failed to open '" << input_path << ": "
276                  << input->StrError();
277       return false;
278     }
279     if (!concat_file->CopyAllFrom(*input)) {
280       LOG(ERROR) << "Failed to copy from '" << input_path << "' to '"
281                  << concat_file_path << "'";
282       return false;
283     }
284   }
285   return true;
286 }
287 
RepackBootImage(const Avb & avb,const std::string & new_kernel_path,const std::string & boot_image_path,const std::string & new_boot_image_path,const std::string & build_dir)288 Result<void> RepackBootImage(const Avb& avb,
289                              const std::string& new_kernel_path,
290                              const std::string& boot_image_path,
291                              const std::string& new_boot_image_path,
292                              const std::string& build_dir) {
293   CF_EXPECT(UnpackBootImage(boot_image_path, build_dir));
294 
295   std::string boot_params = ReadFile(build_dir + "/boot_params");
296   auto kernel_cmdline = ExtractValue(boot_params, "command line args: ");
297   LOG(DEBUG) << "Cmdline from boot image is " << kernel_cmdline;
298 
299   auto tmp_boot_image_path = new_boot_image_path + TMP_EXTENSION;
300   auto repack_path = HostBinaryPath("mkbootimg");
301   Command repack_cmd(repack_path);
302   repack_cmd.AddParameter("--kernel");
303   repack_cmd.AddParameter(new_kernel_path);
304   repack_cmd.AddParameter("--ramdisk");
305   repack_cmd.AddParameter(build_dir + "/ramdisk");
306   repack_cmd.AddParameter("--header_version");
307   repack_cmd.AddParameter("4");
308   repack_cmd.AddParameter("--cmdline");
309   repack_cmd.AddParameter(kernel_cmdline);
310   repack_cmd.AddParameter("-o");
311   repack_cmd.AddParameter(tmp_boot_image_path);
312   int result = repack_cmd.Start().Wait();
313   CF_EXPECT(result == 0, "Unable to run mkbootimg. Exited with status " << result);
314 
315   if (FileSize(tmp_boot_image_path) <= FileSize(boot_image_path)) {
316     CF_EXPECT(avb.AddHashFooter(tmp_boot_image_path, "boot", FileSize(boot_image_path)));
317   } else {
318     CF_EXPECT(avb.AddHashFooter(tmp_boot_image_path, "boot", 0));
319   }
320   CF_EXPECT(DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path));
321 
322   return {};
323 }
324 
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)325 bool RepackVendorBootImage(const std::string& new_ramdisk,
326                            const std::string& vendor_boot_image_path,
327                            const std::string& new_vendor_boot_image_path,
328                            const std::string& unpack_dir,
329                            bool bootconfig_supported) {
330   if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
331       false) {
332     return false;
333   }
334 
335   std::string ramdisk_path;
336   if (new_ramdisk.size()) {
337     ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
338     if (!FileExists(ramdisk_path)) {
339       RepackVendorRamdisk(new_ramdisk,
340                           unpack_dir + "/" + kConcatenatedVendorRamdisk,
341                           ramdisk_path, unpack_dir);
342     }
343   } else {
344     ramdisk_path = unpack_dir + "/" + kConcatenatedVendorRamdisk;
345   }
346 
347   std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
348   LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
349              << bootconfig;
350   std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
351   auto kernel_cmdline =
352       ExtractValue(vendor_boot_params, "vendor command line args: ") +
353       (bootconfig_supported
354            ? ""
355            : " " + android::base::StringReplace(bootconfig, "\n", " ", true));
356   if (!bootconfig_supported) {
357     // TODO(b/182417593): Until we pass the module parameters through
358     // modules.options, we pass them through bootconfig using
359     // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
360     // rename them back to the old cmdline version
361     kernel_cmdline = android::base::StringReplace(
362         kernel_cmdline, " kernel.", " ", true);
363   }
364   LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
365 
366   auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
367   auto repack_path = HostBinaryPath("mkbootimg");
368   Command repack_cmd(repack_path);
369   repack_cmd.AddParameter("--vendor_ramdisk");
370   repack_cmd.AddParameter(ramdisk_path);
371   repack_cmd.AddParameter("--header_version");
372   repack_cmd.AddParameter("4");
373   repack_cmd.AddParameter("--vendor_cmdline");
374   repack_cmd.AddParameter(kernel_cmdline);
375   repack_cmd.AddParameter("--vendor_boot");
376   repack_cmd.AddParameter(tmp_vendor_boot_image_path);
377   repack_cmd.AddParameter("--dtb");
378   repack_cmd.AddParameter(unpack_dir + "/dtb");
379   if (bootconfig_supported) {
380     repack_cmd.AddParameter("--vendor_bootconfig");
381     repack_cmd.AddParameter(unpack_dir + "/bootconfig");
382   }
383 
384   int success = repack_cmd.Start().Wait();
385   if (success != 0) {
386     LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
387     return false;
388   }
389 
390   auto avbtool = Avb(AvbToolBinary());
391   Result<void> result =
392       avbtool.AddHashFooter(tmp_vendor_boot_image_path, "vendor_boot",
393                             FileSize(vendor_boot_image_path));
394   if (!result.ok()) {
395     LOG(ERROR) << result.error().Trace();
396     return false;
397   }
398 
399   return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
400 }
401 
RepackVendorBootImageWithEmptyRamdisk(const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,bool bootconfig_supported)402 bool RepackVendorBootImageWithEmptyRamdisk(
403     const std::string& vendor_boot_image_path,
404     const std::string& new_vendor_boot_image_path,
405     const std::string& unpack_dir, bool bootconfig_supported) {
406   auto empty_ramdisk_file =
407       SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
408   return RepackVendorBootImage(
409       unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
410       new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
411 }
412 
RepackGem5BootImage(const std::string & initrd_path,const std::string & bootconfig_path,const std::string & unpack_dir,const std::string & input_ramdisk_path)413 void RepackGem5BootImage(const std::string& initrd_path,
414                          const std::string& bootconfig_path,
415                          const std::string& unpack_dir,
416                          const std::string& input_ramdisk_path) {
417   // Simulate per-instance what the bootloader would usually do
418   // Since on other devices this runs every time, just do it here every time
419   std::ofstream final_rd(initrd_path,
420                          std::ios_base::binary | std::ios_base::trunc);
421 
422   std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
423                              std::ios_base::binary);
424   std::string new_ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
425   // Test to make sure new ramdisk hasn't already been repacked if input ramdisk is provided
426   if (FileExists(input_ramdisk_path) && !FileExists(new_ramdisk_path)) {
427     RepackVendorRamdisk(input_ramdisk_path,
428                         unpack_dir + "/" + kConcatenatedVendorRamdisk,
429                         new_ramdisk_path, unpack_dir);
430   }
431   std::ifstream vendor_boot_ramdisk(FileExists(new_ramdisk_path) ? new_ramdisk_path : unpack_dir +
432                                     "/concatenated_vendor_ramdisk",
433                                     std::ios_base::binary);
434 
435   std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
436                                        std::ios_base::binary |
437                                        std::ios_base::ate);
438 
439   auto vb_size = vendor_boot_bootconfig.tellg();
440   vendor_boot_bootconfig.seekg(0);
441 
442   std::ifstream persistent_bootconfig(bootconfig_path,
443                                       std::ios_base::binary |
444                                       std::ios_base::ate);
445 
446   auto pb_size = persistent_bootconfig.tellg();
447   persistent_bootconfig.seekg(0);
448 
449   // Build the bootconfig string, trim it, and write the length, checksum
450   // and trailer bytes
451 
452   std::string bootconfig =
453     "androidboot.slot_suffix=_a\n"
454     "androidboot.force_normal_boot=1\n"
455     "androidboot.verifiedbootstate=orange\n";
456   auto bootconfig_size = bootconfig.size();
457   bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
458   vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
459   persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
460   // Trim the block size padding from the persistent bootconfig
461   bootconfig.erase(bootconfig.find_last_not_of('\0'));
462 
463   // Write out the ramdisks and bootconfig blocks
464   final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
465            << bootconfig;
466 
467   // Append bootconfig length
468   bootconfig_size = bootconfig.size();
469   final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
470                  sizeof(uint32_t));
471 
472   // Append bootconfig checksum
473   uint32_t bootconfig_csum = 0;
474   for (auto i = 0; i < bootconfig_size; i++) {
475     bootconfig_csum += bootconfig[i];
476   }
477   final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
478                  sizeof(uint32_t));
479 
480   // Append bootconfig trailer
481   final_rd << "#BOOTCONFIG\n";
482   final_rd.close();
483 }
484 
485 // TODO(290586882) switch this function to rely on avb footers instead of
486 // the os version field in the boot image header.
487 // https://source.android.com/docs/core/architecture/bootloader/boot-image-header
ReadAndroidVersionFromBootImage(const std::string & temp_dir_parent,const std::string & boot_image_path)488 Result<std::string> ReadAndroidVersionFromBootImage(
489     const std::string& temp_dir_parent, const std::string& boot_image_path) {
490   std::string tmp_dir = temp_dir_parent + "/XXXXXXX";
491   if (!mkdtemp(tmp_dir.data())) {
492     return CF_ERR("boot image unpack dir could not be created");
493   }
494   android::base::ScopeGuard delete_dir([tmp_dir]() {
495     Result<void> remove_res = RecursivelyRemoveDirectory(tmp_dir);
496     if (!remove_res.ok()) {
497       LOG(ERROR) << "Failed to delete temp dir '" << tmp_dir << '"';
498       LOG(ERROR) << remove_res.error().FormatForEnv();
499     }
500   });
501 
502   CF_EXPECTF(GetAvbMetadataFromBootImage(boot_image_path, tmp_dir),
503              "'{}' boot image unpack into '{}' failed", boot_image_path,
504              tmp_dir);
505 
506   std::string boot_params = ReadFile(tmp_dir + "/boot_params");
507 
508   std::string os_version =
509       ExtractValue(boot_params, "Prop: com.android.build.boot.os_version -> ");
510   // if the OS version is "None", or the prop does not exist, it wasn't set
511   // when the boot image was made.
512   if (os_version == "None" || os_version.empty()) {
513     LOG(INFO) << "Could not extract os version from " << boot_image_path
514               << ". Defaulting to 0.0.0.";
515     return "0.0.0";
516   }
517 
518   // os_version returned above is surrounded by single quotes. Removing the
519   // single quotes.
520   os_version.erase(remove(os_version.begin(), os_version.end(), '\''),
521                    os_version.end());
522 
523   std::regex re("[1-9][0-9]*([.][0-9]+)*");
524   CF_EXPECT(std::regex_match(os_version, re), "Version string is not a valid version \"" + os_version + "\"");
525   return os_version;
526 }
527 } // namespace cuttlefish
528