• 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 <sstream>
25 
26 #include <android-base/logging.h>
27 #include <android-base/strings.h>
28 
29 #include "common/libs/utils/files.h"
30 #include "common/libs/utils/subprocess.h"
31 
32 const char TMP_EXTENSION[] = ".tmp";
33 const char CPIO_EXT[] = ".cpio";
34 const char TMP_RD_DIR[] = "stripped_ramdisk_dir";
35 const char STRIPPED_RD[] = "stripped_ramdisk";
36 const char CONCATENATED_VENDOR_RAMDISK[] = "concatenated_vendor_ramdisk";
37 namespace cuttlefish {
38 namespace {
ExtractValue(const std::string & dictionary,const std::string & key)39 std::string ExtractValue(const std::string& dictionary, const std::string& key) {
40   std::size_t index = dictionary.find(key);
41   if (index != std::string::npos) {
42     std::size_t end_index = dictionary.find('\n', index + key.length());
43     if (end_index != std::string::npos) {
44       return dictionary.substr(index + key.length(),
45           end_index - index - key.length());
46     }
47   }
48   return "";
49 }
50 
51 // Though it is just as fast to overwrite the existing boot images with the newly generated ones,
52 // the cuttlefish composite disk generator checks the age of each of the components and
53 // regenerates the disk outright IF any one of the components is younger/newer than the current
54 // composite disk. If this file overwrite occurs, that condition is fulfilled. This action then
55 // causes data in the userdata partition from previous boots to be lost (which is not expected by
56 // the user if they've been booting the same kernel/ramdisk combination repeatedly).
57 // 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)58 bool DeleteTmpFileIfNotChanged(const std::string& tmp_file, const std::string& current_file) {
59   if (!FileExists(current_file) ||
60       ReadFile(current_file) != ReadFile(tmp_file)) {
61     if (!RenameFile(tmp_file, current_file)) {
62       LOG(ERROR) << "Unable to delete " << current_file;
63       return false;
64     }
65     LOG(DEBUG) << "Updated " << current_file;
66   } else {
67     LOG(DEBUG) << "Didn't update " << current_file;
68     RemoveFile(tmp_file);
69   }
70 
71   return true;
72 }
73 
UnpackBootImage(const std::string & boot_image_path,const std::string & unpack_dir)74 bool UnpackBootImage(const std::string& boot_image_path,
75                      const std::string& unpack_dir) {
76   auto unpack_path = HostBinaryPath("unpack_bootimg");
77   Command unpack_cmd(unpack_path);
78   unpack_cmd.AddParameter("--boot_img");
79   unpack_cmd.AddParameter(boot_image_path);
80   unpack_cmd.AddParameter("--out");
81   unpack_cmd.AddParameter(unpack_dir);
82 
83   auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
84   if (!output_file->IsOpen()) {
85     LOG(ERROR) << "Unable to create intermediate boot params file: "
86                << output_file->StrError();
87     return false;
88   }
89   unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
90 
91   int success = unpack_cmd.Start().Wait();
92   if (success != 0) {
93     LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
94                << success;
95     return false;
96   }
97   return true;
98 }
99 
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)100 void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
101                          const std::string& original_ramdisk_path,
102                          const std::string& new_ramdisk_path,
103                          const std::string& build_dir) {
104   int success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") + " -c -d -l " +
105                         original_ramdisk_path + " > " + original_ramdisk_path + CPIO_EXT});
106   CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
107 
108   const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
109   success =
110       mkdir(ramdisk_stage_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
111   CHECK(success == 0) << "Could not mkdir \"" << ramdisk_stage_dir
112                       << "\", error was " << strerror(errno);
113 
114   success = execute(
115       {"/bin/bash", "-c",
116        "(cd " + ramdisk_stage_dir + " && while " + HostBinaryPath("toybox") +
117            " cpio -idu; do :; done) < " + original_ramdisk_path + CPIO_EXT});
118   CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
119                       << success;
120 
121   success = execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
122   CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
123                       << "Exited with status " << success;
124 
125   const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
126   success = execute({"/bin/bash", "-c",
127                      HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
128                          " > " + stripped_ramdisk_path + CPIO_EXT});
129   CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
130                       << success;
131 
132   success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") +
133                      " -c -l -12 --favor-decSpeed " + stripped_ramdisk_path + CPIO_EXT + " > " +
134                      stripped_ramdisk_path});
135   CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
136 
137   // Concatenates the stripped ramdisk and input ramdisk and places the result at new_ramdisk_path
138   std::ofstream final_rd(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
139   std::ifstream ramdisk_a(stripped_ramdisk_path, std::ios_base::binary);
140   std::ifstream ramdisk_b(kernel_modules_ramdisk_path, std::ios_base::binary);
141   final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
142 }
143 
UnpackVendorBootImageIfNotUnpacked(const std::string & vendor_boot_image_path,const std::string & unpack_dir)144 bool UnpackVendorBootImageIfNotUnpacked(
145     const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
146   // the vendor boot params file is created during the first unpack. If it's
147   // already there, a unpack has occurred and there's no need to repeat the
148   // process.
149   if (FileExists(unpack_dir + "/vendor_boot_params")) {
150     return true;
151   }
152 
153   auto unpack_path = HostBinaryPath("unpack_bootimg");
154   Command unpack_cmd(unpack_path);
155   unpack_cmd.AddParameter("--boot_img");
156   unpack_cmd.AddParameter(vendor_boot_image_path);
157   unpack_cmd.AddParameter("--out");
158   unpack_cmd.AddParameter(unpack_dir);
159   auto output_file = SharedFD::Creat(unpack_dir + "/vendor_boot_params", 0666);
160   if (!output_file->IsOpen()) {
161     LOG(ERROR) << "Unable to create intermediate vendor boot params file: "
162                << output_file->StrError();
163     return false;
164   }
165   unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
166   int success = unpack_cmd.Start().Wait();
167   if (success != 0) {
168     LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status " << success;
169     return false;
170   }
171 
172   // Concatenates all vendor ramdisk into one single ramdisk.
173   Command concat_cmd("/bin/bash");
174   concat_cmd.AddParameter("-c");
175   concat_cmd.AddParameter("cat " + unpack_dir + "/vendor_ramdisk*");
176   auto concat_file =
177       SharedFD::Creat(unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK, 0666);
178   if (!concat_file->IsOpen()) {
179     LOG(ERROR) << "Unable to create concatenated vendor ramdisk file: "
180                << concat_file->StrError();
181     return false;
182   }
183   concat_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, concat_file);
184   success = concat_cmd.Start().Wait();
185   if (success != 0) {
186     LOG(ERROR) << "Unable to run cat. Exited with status " << success;
187     return false;
188   }
189   return true;
190 }
191 
192 }  // namespace
193 
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)194 bool RepackBootImage(const std::string& new_kernel_path,
195                      const std::string& boot_image_path,
196                      const std::string& new_boot_image_path,
197                      const std::string& build_dir) {
198   if (UnpackBootImage(boot_image_path, build_dir) == false) {
199     return false;
200   }
201 
202   std::string boot_params = ReadFile(build_dir + "/boot_params");
203   auto kernel_cmdline = ExtractValue(boot_params, "command line args: ");
204   LOG(DEBUG) << "Cmdline from boot image is " << kernel_cmdline;
205 
206   auto tmp_boot_image_path = new_boot_image_path + TMP_EXTENSION;
207   auto repack_path = HostBinaryPath("mkbootimg");
208   Command repack_cmd(repack_path);
209   repack_cmd.AddParameter("--kernel");
210   repack_cmd.AddParameter(new_kernel_path);
211   repack_cmd.AddParameter("--ramdisk");
212   repack_cmd.AddParameter(build_dir + "/ramdisk");
213   repack_cmd.AddParameter("--header_version");
214   repack_cmd.AddParameter("4");
215   repack_cmd.AddParameter("--cmdline");
216   repack_cmd.AddParameter(kernel_cmdline);
217   repack_cmd.AddParameter("-o");
218   repack_cmd.AddParameter(tmp_boot_image_path);
219   int success = repack_cmd.Start().Wait();
220   if (success != 0) {
221     LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
222     return false;
223   }
224 
225   auto fd = SharedFD::Open(tmp_boot_image_path, O_RDWR);
226   auto original_size = FileSize(boot_image_path);
227   CHECK(fd->Truncate(original_size) == 0)
228     << "`truncate --size=" << original_size << " " << tmp_boot_image_path << "` "
229     << "failed: " << fd->StrError();
230 
231   return DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path);
232 }
233 
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,const std::string & repack_dir,const std::vector<std::string> & bootconfig_args,bool bootconfig_supported)234 bool RepackVendorBootImage(const std::string& new_ramdisk,
235                            const std::string& vendor_boot_image_path,
236                            const std::string& new_vendor_boot_image_path,
237                            const std::string& unpack_dir,
238                            const std::string& repack_dir,
239                            const std::vector<std::string>& bootconfig_args,
240                            bool bootconfig_supported) {
241   if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
242       false) {
243     return false;
244   }
245 
246   std::string ramdisk_path;
247   if (new_ramdisk.size()) {
248     ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
249     if (!FileExists(ramdisk_path)) {
250       RepackVendorRamdisk(new_ramdisk,
251                           unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
252                           ramdisk_path, unpack_dir);
253     }
254   } else {
255     ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
256   }
257 
258   auto bootconfig_fd = SharedFD::Creat(repack_dir + "/bootconfig", 0666);
259   if (!bootconfig_fd->IsOpen()) {
260     LOG(ERROR) << "Unable to create intermediate bootconfig file: "
261                << bootconfig_fd->StrError();
262     return false;
263   }
264   std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
265   bootconfig_fd->Write(bootconfig.c_str(), bootconfig.size());
266   LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
267              << ReadFile(repack_dir + "/bootconfig");
268   std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
269   auto kernel_cmdline =
270       ExtractValue(vendor_boot_params, "vendor command line args: ") +
271       (bootconfig_supported
272            ? ""
273            : " " + android::base::StringReplace(bootconfig, "\n", " ", true) +
274                  " " + android::base::Join(bootconfig_args, " "));
275   if (!bootconfig_supported) {
276     // TODO(b/182417593): Until we pass the module parameters through
277     // modules.options, we pass them through bootconfig using
278     // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
279     // rename them back to the old cmdline version
280     kernel_cmdline = android::base::StringReplace(
281         kernel_cmdline, " kernel.", " ", true);
282   }
283   LOG(DEBUG) << "Cmdline from vendor boot image and config is "
284              << kernel_cmdline;
285 
286   auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
287   auto repack_path = HostBinaryPath("mkbootimg");
288   Command repack_cmd(repack_path);
289   repack_cmd.AddParameter("--vendor_ramdisk");
290   repack_cmd.AddParameter(ramdisk_path);
291   repack_cmd.AddParameter("--header_version");
292   repack_cmd.AddParameter("4");
293   repack_cmd.AddParameter("--vendor_cmdline");
294   repack_cmd.AddParameter(kernel_cmdline);
295   repack_cmd.AddParameter("--vendor_boot");
296   repack_cmd.AddParameter(tmp_vendor_boot_image_path);
297   repack_cmd.AddParameter("--dtb");
298   repack_cmd.AddParameter(unpack_dir + "/dtb");
299   if (bootconfig_supported) {
300     repack_cmd.AddParameter("--vendor_bootconfig");
301     repack_cmd.AddParameter(repack_dir + "/bootconfig");
302   }
303 
304   int success = repack_cmd.Start().Wait();
305   if (success != 0) {
306     LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
307     return false;
308   }
309 
310   auto fd = SharedFD::Open(tmp_vendor_boot_image_path, O_RDWR);
311   auto original_size = FileSize(vendor_boot_image_path);
312   CHECK(fd->Truncate(original_size) == 0)
313     << "`truncate --size=" << original_size << " " << tmp_vendor_boot_image_path << "` "
314     << "failed: " << fd->StrError();
315 
316   return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
317 }
318 
RepackVendorBootImageWithEmptyRamdisk(const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,const std::string & repack_dir,const std::vector<std::string> & bootconfig_args,bool bootconfig_supported)319 bool RepackVendorBootImageWithEmptyRamdisk(
320     const std::string& vendor_boot_image_path,
321     const std::string& new_vendor_boot_image_path,
322     const std::string& unpack_dir, const std::string& repack_dir,
323     const std::vector<std::string>& bootconfig_args,
324     bool bootconfig_supported) {
325   auto empty_ramdisk_file =
326       SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
327   return RepackVendorBootImage(
328       unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
329       new_vendor_boot_image_path, unpack_dir, repack_dir, bootconfig_args,
330       bootconfig_supported);
331 }
332 } // namespace cuttlefish
333