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