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