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
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)74 void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
75 const std::string& original_ramdisk_path,
76 const std::string& new_ramdisk_path,
77 const std::string& build_dir) {
78 int success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") + " -c -d -l " +
79 original_ramdisk_path + " > " + original_ramdisk_path + CPIO_EXT});
80 CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
81
82 const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
83 success =
84 mkdir(ramdisk_stage_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
85 CHECK(success == 0) << "Could not mkdir \"" << ramdisk_stage_dir
86 << "\", error was " << strerror(errno);
87
88 success = execute(
89 {"/bin/bash", "-c",
90 "(cd " + ramdisk_stage_dir + " && while " + HostBinaryPath("toybox") +
91 " cpio -idu; do :; done) < " + original_ramdisk_path + CPIO_EXT});
92 CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
93 << success;
94
95 success = execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
96 CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
97 << "Exited with status " << success;
98
99 const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
100 success = execute({"/bin/bash", "-c",
101 HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
102 " > " + stripped_ramdisk_path + CPIO_EXT});
103 CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
104 << success;
105
106 success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") +
107 " -c -l -12 --favor-decSpeed " + stripped_ramdisk_path + CPIO_EXT + " > " +
108 stripped_ramdisk_path});
109 CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
110
111 // Concatenates the stripped ramdisk and input ramdisk and places the result at new_ramdisk_path
112 std::ofstream final_rd(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
113 std::ifstream ramdisk_a(stripped_ramdisk_path, std::ios_base::binary);
114 std::ifstream ramdisk_b(kernel_modules_ramdisk_path, std::ios_base::binary);
115 final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
116 }
117
118 } // namespace
119
UnpackBootImage(const std::string & boot_image_path,const std::string & unpack_dir)120 bool UnpackBootImage(const std::string& boot_image_path,
121 const std::string& unpack_dir) {
122 auto unpack_path = HostBinaryPath("unpack_bootimg");
123 Command unpack_cmd(unpack_path);
124 unpack_cmd.AddParameter("--boot_img");
125 unpack_cmd.AddParameter(boot_image_path);
126 unpack_cmd.AddParameter("--out");
127 unpack_cmd.AddParameter(unpack_dir);
128
129 auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
130 if (!output_file->IsOpen()) {
131 LOG(ERROR) << "Unable to create intermediate boot params file: "
132 << output_file->StrError();
133 return false;
134 }
135 unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
136
137 int success = unpack_cmd.Start().Wait();
138 if (success != 0) {
139 LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
140 << success;
141 return false;
142 }
143 return true;
144 }
145
UnpackVendorBootImageIfNotUnpacked(const std::string & vendor_boot_image_path,const std::string & unpack_dir)146 bool UnpackVendorBootImageIfNotUnpacked(
147 const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
148 // the vendor boot params file is created during the first unpack. If it's
149 // already there, a unpack has occurred and there's no need to repeat the
150 // process.
151 if (FileExists(unpack_dir + "/vendor_boot_params")) {
152 return true;
153 }
154
155 auto unpack_path = HostBinaryPath("unpack_bootimg");
156 Command unpack_cmd(unpack_path);
157 unpack_cmd.AddParameter("--boot_img");
158 unpack_cmd.AddParameter(vendor_boot_image_path);
159 unpack_cmd.AddParameter("--out");
160 unpack_cmd.AddParameter(unpack_dir);
161 auto output_file = SharedFD::Creat(unpack_dir + "/vendor_boot_params", 0666);
162 if (!output_file->IsOpen()) {
163 LOG(ERROR) << "Unable to create intermediate vendor boot params file: "
164 << output_file->StrError();
165 return false;
166 }
167 unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
168 int success = unpack_cmd.Start().Wait();
169 if (success != 0) {
170 LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status " << success;
171 return false;
172 }
173
174 // Concatenates all vendor ramdisk into one single ramdisk.
175 Command concat_cmd("/bin/bash");
176 concat_cmd.AddParameter("-c");
177 concat_cmd.AddParameter("cat " + unpack_dir + "/vendor_ramdisk*");
178 auto concat_file =
179 SharedFD::Creat(unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK, 0666);
180 if (!concat_file->IsOpen()) {
181 LOG(ERROR) << "Unable to create concatenated vendor ramdisk file: "
182 << concat_file->StrError();
183 return false;
184 }
185 concat_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, concat_file);
186 success = concat_cmd.Start().Wait();
187 if (success != 0) {
188 LOG(ERROR) << "Unable to run cat. Exited with status " << success;
189 return false;
190 }
191 return true;
192 }
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 avbtool_path = HostBinaryPath("avbtool");
226 Command avb_cmd(avbtool_path);
227 avb_cmd.AddParameter("add_hash_footer");
228 avb_cmd.AddParameter("--image");
229 avb_cmd.AddParameter(tmp_boot_image_path);
230 avb_cmd.AddParameter("--partition_size");
231 avb_cmd.AddParameter(FileSize(boot_image_path));
232 avb_cmd.AddParameter("--partition_name");
233 avb_cmd.AddParameter("boot");
234 success = avb_cmd.Start().Wait();
235 if (success != 0) {
236 LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
237 return false;
238 }
239
240 return DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path);
241 }
242
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)243 bool RepackVendorBootImage(const std::string& new_ramdisk,
244 const std::string& vendor_boot_image_path,
245 const std::string& new_vendor_boot_image_path,
246 const std::string& unpack_dir,
247 bool bootconfig_supported) {
248 if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
249 false) {
250 return false;
251 }
252
253 std::string ramdisk_path;
254 if (new_ramdisk.size()) {
255 ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
256 if (!FileExists(ramdisk_path)) {
257 RepackVendorRamdisk(new_ramdisk,
258 unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
259 ramdisk_path, unpack_dir);
260 }
261 } else {
262 ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
263 }
264
265 std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
266 LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
267 << 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 if (!bootconfig_supported) {
275 // TODO(b/182417593): Until we pass the module parameters through
276 // modules.options, we pass them through bootconfig using
277 // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
278 // rename them back to the old cmdline version
279 kernel_cmdline = android::base::StringReplace(
280 kernel_cmdline, " kernel.", " ", true);
281 }
282 LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
283
284 auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
285 auto repack_path = HostBinaryPath("mkbootimg");
286 Command repack_cmd(repack_path);
287 repack_cmd.AddParameter("--vendor_ramdisk");
288 repack_cmd.AddParameter(ramdisk_path);
289 repack_cmd.AddParameter("--header_version");
290 repack_cmd.AddParameter("4");
291 repack_cmd.AddParameter("--vendor_cmdline");
292 repack_cmd.AddParameter(kernel_cmdline);
293 repack_cmd.AddParameter("--vendor_boot");
294 repack_cmd.AddParameter(tmp_vendor_boot_image_path);
295 repack_cmd.AddParameter("--dtb");
296 repack_cmd.AddParameter(unpack_dir + "/dtb");
297 if (bootconfig_supported) {
298 repack_cmd.AddParameter("--vendor_bootconfig");
299 repack_cmd.AddParameter(unpack_dir + "/bootconfig");
300 }
301
302 int success = repack_cmd.Start().Wait();
303 if (success != 0) {
304 LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
305 return false;
306 }
307
308 auto avbtool_path = HostBinaryPath("avbtool");
309 Command avb_cmd(avbtool_path);
310 avb_cmd.AddParameter("add_hash_footer");
311 avb_cmd.AddParameter("--image");
312 avb_cmd.AddParameter(tmp_vendor_boot_image_path);
313 avb_cmd.AddParameter("--partition_size");
314 avb_cmd.AddParameter(FileSize(vendor_boot_image_path));
315 avb_cmd.AddParameter("--partition_name");
316 avb_cmd.AddParameter("vendor_boot");
317 success = avb_cmd.Start().Wait();
318 if (success != 0) {
319 LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
320 return false;
321 }
322
323 return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
324 }
325
RepackVendorBootImageWithEmptyRamdisk(const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,bool bootconfig_supported)326 bool RepackVendorBootImageWithEmptyRamdisk(
327 const std::string& vendor_boot_image_path,
328 const std::string& new_vendor_boot_image_path,
329 const std::string& unpack_dir, bool bootconfig_supported) {
330 auto empty_ramdisk_file =
331 SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
332 return RepackVendorBootImage(
333 unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
334 new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
335 }
336
RepackGem5BootImage(const std::string & initrd_path,const std::string & bootconfig_path,const std::string & unpack_dir)337 void RepackGem5BootImage(const std::string& initrd_path,
338 const std::string& bootconfig_path,
339 const std::string& unpack_dir) {
340 // Simulate per-instance what the bootloader would usually do
341 // Since on other devices this runs every time, just do it here every time
342 std::ofstream final_rd(initrd_path,
343 std::ios_base::binary | std::ios_base::trunc);
344
345 std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
346 std::ios_base::binary);
347 std::ifstream vendor_boot_ramdisk(unpack_dir +
348 "/concatenated_vendor_ramdisk",
349 std::ios_base::binary);
350
351 std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
352 std::ios_base::binary |
353 std::ios_base::ate);
354
355 auto vb_size = vendor_boot_bootconfig.tellg();
356 vendor_boot_bootconfig.seekg(0);
357
358 std::ifstream persistent_bootconfig(bootconfig_path,
359 std::ios_base::binary |
360 std::ios_base::ate);
361
362 auto pb_size = persistent_bootconfig.tellg();
363 persistent_bootconfig.seekg(0);
364
365 // Build the bootconfig string, trim it, and write the length, checksum
366 // and trailer bytes
367
368 std::string bootconfig =
369 "androidboot.slot_suffix=_a\n"
370 "androidboot.force_normal_boot=1\n"
371 "androidboot.verifiedbootstate=orange\n";
372 auto bootconfig_size = bootconfig.size();
373 bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
374 vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
375 persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
376 // Trim the block size padding from the persistent bootconfig
377 bootconfig.erase(bootconfig.find_last_not_of('\0'));
378
379 // Write out the ramdisks and bootconfig blocks
380 final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
381 << bootconfig;
382
383 // Append bootconfig length
384 bootconfig_size = bootconfig.size();
385 final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
386 sizeof(uint32_t));
387
388 // Append bootconfig checksum
389 uint32_t bootconfig_csum = 0;
390 for (auto i = 0; i < bootconfig_size; i++) {
391 bootconfig_csum += bootconfig[i];
392 }
393 final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
394 sizeof(uint32_t));
395
396 // Append bootconfig trailer
397 final_rd << "#BOOTCONFIG\n";
398 final_rd.close();
399 }
400 } // namespace cuttlefish
401