1 //
2 // Copyright (C) 2019 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 #include "host/commands/assemble_cvd/super_image_mixer.h"
17
18 #include <sys/stat.h>
19
20 #include <algorithm>
21 #include <array>
22 #include <memory>
23 #include <string>
24 #include <string_view>
25 #include <unordered_set>
26 #include <utility>
27 #include <vector>
28
29 #include <android-base/strings.h>
30 #include <android-base/logging.h>
31
32 #include "common/libs/utils/archive.h"
33 #include "common/libs/utils/contains.h"
34 #include "common/libs/utils/files.h"
35 #include "common/libs/utils/result.h"
36 #include "common/libs/utils/subprocess.h"
37 #include "host/commands/assemble_cvd/misc_info.h"
38 #include "host/libs/avb/avb.h"
39 #include "host/libs/config/config_utils.h"
40 #include "host/libs/config/cuttlefish_config.h"
41 #include "host/libs/config/fetcher_config.h"
42 #include "host/libs/config/known_paths.h"
43
44 namespace cuttlefish {
45 namespace {
46
47 constexpr char kMiscInfoPath[] = "META/misc_info.txt";
48 constexpr char kDynamicPartitionsPath[] = "META/dynamic_partitions_info.txt";
49 constexpr std::array kVendorTargetImages = {
50 "IMAGES/boot.img", "IMAGES/dtbo.img",
51 "IMAGES/init_boot.img", "IMAGES/odm.img",
52 "IMAGES/odm_dlkm.img", "IMAGES/recovery.img",
53 "IMAGES/system_dlkm.img", "IMAGES/userdata.img",
54 "IMAGES/vbmeta.img", "IMAGES/vbmeta_system_dlkm.img",
55 "IMAGES/vbmeta_vendor.img", "IMAGES/vbmeta_vendor_dlkm.img",
56 "IMAGES/vendor.img", "IMAGES/vendor_boot.img",
57 "IMAGES/vendor_dlkm.img", "IMAGES/vendor_kernel_boot.img",
58 };
59 constexpr std::array kVendorTargetBuildProps = {
60 "ODM/build.prop",
61 "ODM/etc/build.prop",
62 "VENDOR/build.prop",
63 "VENDOR/etc/build.prop",
64 };
65
66 struct RebuildPaths {
67 std::string vendor_target_zip;
68 std::string system_target_zip;
69 std::string combined_target_zip;
70 std::string super_image_output;
71 std::string vbmeta_image_output;
72 };
73
74 struct TargetFiles {
75 Archive vendor_zip;
76 Archive system_zip;
77 std::vector<std::string> vendor_contents;
78 std::vector<std::string> system_contents;
79 };
80
81 struct Extracted {
82 std::set<std::string> images;
83 std::vector<std::string> system_partitions;
84 };
85
FindImports(Archive * archive,const std::string & build_prop_file)86 void FindImports(Archive* archive, const std::string& build_prop_file) {
87 auto contents = archive->ExtractToMemory(build_prop_file);
88 auto lines = android::base::Split(contents, "\n");
89 for (const auto& line : lines) {
90 auto parts = android::base::Split(line, " ");
91 if (parts.size() >= 2 && parts[0] == "import") {
92 LOG(DEBUG) << build_prop_file << ": " << line;
93 }
94 }
95 }
96
IsTargetFilesImage(const std::string & filename)97 bool IsTargetFilesImage(const std::string& filename) {
98 return android::base::StartsWith(filename, "IMAGES/") &&
99 android::base::EndsWith(filename, ".img");
100 }
101
IsTargetFilesBuildProp(const std::string & filename)102 bool IsTargetFilesBuildProp(const std::string& filename) {
103 return android::base::EndsWith(filename, "build.prop");
104 }
105
GetPartitionNameFromPath(const std::string & path)106 Result<std::string> GetPartitionNameFromPath(const std::string& path) {
107 std::string_view result(path);
108 CF_EXPECTF(
109 android::base::ConsumePrefix(&result, "IMAGES/"),
110 "target_files filepath {} expected to be in the \"IMAGES\" directory",
111 path);
112 CF_EXPECTF(android::base::ConsumeSuffix(&result, ".img"),
113 "target_files filepath {} expected to be a \".img\" file", path);
114 return std::string(result);
115 }
116
GetTargetFiles(const std::string & vendor_zip_path,const std::string & system_zip_path)117 Result<TargetFiles> GetTargetFiles(const std::string& vendor_zip_path,
118 const std::string& system_zip_path) {
119 auto result = TargetFiles{
120 .vendor_zip = Archive(vendor_zip_path),
121 .system_zip = Archive(system_zip_path),
122 };
123 result.vendor_contents = result.vendor_zip.Contents();
124 result.system_contents = result.system_zip.Contents();
125 CF_EXPECTF(!result.vendor_contents.empty(), "Could not open {}",
126 vendor_zip_path);
127 CF_EXPECTF(!result.system_contents.empty(), "Could not open {}",
128 system_zip_path);
129 return result;
130 }
131
CombineDynamicPartitionsInfo(TargetFiles & target_files,const std::set<std::string> & extracted_images)132 Result<MiscInfo> CombineDynamicPartitionsInfo(
133 TargetFiles& target_files, const std::set<std::string>& extracted_images) {
134 CF_EXPECTF(Contains(target_files.vendor_contents, kDynamicPartitionsPath),
135 "Vendor target files zip does not contain {}",
136 kDynamicPartitionsPath);
137 CF_EXPECTF(Contains(target_files.system_contents, kDynamicPartitionsPath),
138 "System target files zip does not contain {}",
139 kDynamicPartitionsPath);
140
141 const MiscInfo vendor_dp_info = CF_EXPECT(ParseMiscInfo(
142 target_files.vendor_zip.ExtractToMemory(kDynamicPartitionsPath)));
143 const MiscInfo system_dp_info = CF_EXPECT(ParseMiscInfo(
144 target_files.system_zip.ExtractToMemory(kDynamicPartitionsPath)));
145
146 return CF_EXPECT(GetCombinedDynamicPartitions(vendor_dp_info, system_dp_info,
147 extracted_images));
148 }
149
CombineMiscInfo(TargetFiles & target_files,const std::string & misc_output_path,const std::set<std::string> & extracted_images,const std::vector<std::string> & system_partitions)150 Result<MiscInfo> CombineMiscInfo(
151 TargetFiles& target_files, const std::string& misc_output_path,
152 const std::set<std::string>& extracted_images,
153 const std::vector<std::string>& system_partitions) {
154 CF_EXPECTF(Contains(target_files.vendor_contents, kMiscInfoPath),
155 "Vendor target files zip does not contain {}", kMiscInfoPath);
156 CF_EXPECTF(Contains(target_files.system_contents, kMiscInfoPath),
157 "System target files zip does not contain {}", kMiscInfoPath);
158
159 const MiscInfo vendor_misc = CF_EXPECT(
160 ParseMiscInfo(target_files.vendor_zip.ExtractToMemory(kMiscInfoPath)));
161 const MiscInfo system_misc = CF_EXPECT(
162 ParseMiscInfo(target_files.system_zip.ExtractToMemory(kMiscInfoPath)));
163
164 const auto combined_dp_info =
165 CF_EXPECT(CombineDynamicPartitionsInfo(target_files, extracted_images));
166 const auto output_misc = CF_EXPECT(MergeMiscInfos(
167 vendor_misc, system_misc, combined_dp_info, system_partitions));
168
169 CF_EXPECT(WriteMiscInfo(output_misc, misc_output_path));
170 return std::move(output_misc);
171 }
172
ExtractTargetFiles(TargetFiles & target_files,const std::string & combined_output_path)173 Result<Extracted> ExtractTargetFiles(TargetFiles& target_files,
174 const std::string& combined_output_path) {
175 Extracted extracted;
176 for (const auto& name : target_files.vendor_contents) {
177 if (!IsTargetFilesImage(name)) {
178 continue;
179 } else if (!Contains(kVendorTargetImages, name)) {
180 continue;
181 }
182 LOG(DEBUG) << "Writing " << name << " from vendor target";
183 CF_EXPECT(
184 target_files.vendor_zip.ExtractFiles({name}, combined_output_path),
185 "Failed to extract " << name << " from the vendor target zip");
186 extracted.images.emplace(CF_EXPECT(GetPartitionNameFromPath(name)));
187 }
188 for (const auto& name : target_files.vendor_contents) {
189 if (!IsTargetFilesBuildProp(name)) {
190 continue;
191 } else if (!Contains(kVendorTargetBuildProps, name)) {
192 continue;
193 }
194 FindImports(&target_files.vendor_zip, name);
195 LOG(DEBUG) << "Writing " << name << " from vendor target";
196 CF_EXPECT(
197 target_files.vendor_zip.ExtractFiles({name}, combined_output_path),
198 "Failed to extract " << name << " from the vendor target zip");
199 }
200 LOG(INFO) << "Completed extracting images from vendor.";
201
202 for (const auto& name : target_files.system_contents) {
203 if (!IsTargetFilesImage(name)) {
204 continue;
205 } else if (Contains(kVendorTargetImages, name)) {
206 continue;
207 }
208 LOG(DEBUG) << "Writing " << name << " from system target";
209 CF_EXPECT(
210 target_files.system_zip.ExtractFiles({name}, combined_output_path),
211 "Failed to extract " << name << " from the system target zip");
212 const auto partition = CF_EXPECT(GetPartitionNameFromPath(name));
213 extracted.images.emplace(partition);
214 extracted.system_partitions.emplace_back(partition);
215 }
216 for (const auto& name : target_files.system_contents) {
217 if (!IsTargetFilesBuildProp(name)) {
218 continue;
219 } else if (Contains(kVendorTargetBuildProps, name)) {
220 continue;
221 }
222 FindImports(&target_files.system_zip, name);
223 LOG(DEBUG) << "Writing " << name << " from system target";
224 CF_EXPECT(
225 target_files.system_zip.ExtractFiles({name}, combined_output_path),
226 "Failed to extract " << name << " from the system target zip");
227 }
228 LOG(INFO) << "Completed extracting images from system.";
229 return extracted;
230 }
231
RegenerateVbmeta(const MiscInfo & misc_info,const std::string & output_path,const std::string & image_path)232 Result<void> RegenerateVbmeta(const MiscInfo& misc_info,
233 const std::string& output_path,
234 const std::string& image_path) {
235 const VbmetaArgs args = CF_EXPECT(GetVbmetaArgs(misc_info, image_path));
236 auto avbtool = Avb(AvbToolBinary(), args.algorithm, args.key_path);
237 CF_EXPECT(avbtool.MakeVbMetaImage(output_path, args.chained_partitions,
238 args.included_partitions,
239 args.extra_arguments));
240 return {};
241 }
242
CombineTargetZipFiles(const RebuildPaths & paths)243 Result<void> CombineTargetZipFiles(const RebuildPaths& paths) {
244 CF_EXPECT(EnsureDirectoryExists(paths.combined_target_zip));
245 CF_EXPECT(EnsureDirectoryExists(paths.combined_target_zip + "/META"));
246 auto target_files = CF_EXPECT(
247 GetTargetFiles(paths.vendor_target_zip, paths.system_target_zip));
248 const auto extracted =
249 CF_EXPECT(ExtractTargetFiles(target_files, paths.combined_target_zip));
250 const auto misc_output_path = paths.combined_target_zip + "/" + kMiscInfoPath;
251 const auto combined_info =
252 CF_EXPECT(CombineMiscInfo(target_files, misc_output_path,
253 extracted.images, extracted.system_partitions));
254 CF_EXPECT(RegenerateVbmeta(combined_info, paths.vbmeta_image_output,
255 paths.combined_target_zip));
256 return {};
257 }
258
BuildSuperImage(const std::string & combined_target_zip,const std::string & output_path)259 bool BuildSuperImage(const std::string& combined_target_zip,
260 const std::string& output_path) {
261 std::string otatools_path = DefaultHostArtifactsPath("");
262 std::string build_super_image_binary = HostBinaryPath("build_super_image");
263 if (!FileExists(build_super_image_binary)) {
264 LOG(ERROR) << "Could not find build_super_image";
265 return false;
266 }
267 return Execute({
268 build_super_image_binary,
269 "--path=" + otatools_path,
270 combined_target_zip,
271 output_path,
272 }) == 0;
273 }
274
TargetFilesZip(const FetcherConfig & fetcher_config,FileSource source)275 std::string TargetFilesZip(const FetcherConfig& fetcher_config,
276 FileSource source) {
277 for (const auto& file_iter : fetcher_config.get_cvd_files()) {
278 const auto& file_path = file_iter.first;
279 const auto& file_info = file_iter.second;
280 if (file_info.source != source) {
281 continue;
282 }
283 std::string expected_filename = "target_files-" + file_iter.second.build_id;
284 if (file_path.find(expected_filename) != std::string::npos) {
285 return file_path;
286 }
287 }
288 return "";
289 }
290
GetRebuildPaths(const FetcherConfig & fetcher_config,const CuttlefishConfig::InstanceSpecific & instance_config)291 Result<RebuildPaths> GetRebuildPaths(
292 const FetcherConfig& fetcher_config,
293 const CuttlefishConfig::InstanceSpecific& instance_config) {
294 // In SuperImageNeedsRebuilding, it already checked that both paths either
295 // exist or do not exist, together Here, we only check if there is an input
296 // path
297 std::string default_target_zip = instance_config.default_target_zip();
298 std::string system_target_zip = instance_config.system_target_zip();
299 if (default_target_zip == "" || default_target_zip == "unset") {
300 default_target_zip =
301 TargetFilesZip(fetcher_config, FileSource::DEFAULT_BUILD);
302 CF_EXPECT(default_target_zip != "",
303 "Unable to find default target zip file.");
304
305 system_target_zip =
306 TargetFilesZip(fetcher_config, FileSource::SYSTEM_BUILD);
307 CF_EXPECT(system_target_zip != "", "Unable to find system target zip file.");
308 }
309 return RebuildPaths{
310 .vendor_target_zip = default_target_zip,
311 .system_target_zip = system_target_zip,
312 // TODO(schuffelen): Use cuttlefish_assembly
313 .combined_target_zip =
314 instance_config.PerInstanceInternalPath("target_combined"),
315 .super_image_output = instance_config.new_super_image(),
316 .vbmeta_image_output = instance_config.new_vbmeta_image(),
317 };
318 }
319
RebuildSuperImage(const RebuildPaths & paths)320 Result<void> RebuildSuperImage(const RebuildPaths& paths) {
321 // TODO(schuffelen): Use otatools/bin/merge_target_files
322 CF_EXPECT(CombineTargetZipFiles(paths),
323 "Could not combine target zip files.");
324
325 CF_EXPECT(
326 BuildSuperImage(paths.combined_target_zip, paths.super_image_output),
327 "Could not write the final output super image.");
328 return {};
329 }
330
331 class SuperImageRebuilderImpl : public SuperImageRebuilder {
332 public:
INJECT(SuperImageRebuilderImpl (const FetcherConfig & fetcher_config,const CuttlefishConfig::InstanceSpecific & instance))333 INJECT(SuperImageRebuilderImpl(
334 const FetcherConfig& fetcher_config,
335 const CuttlefishConfig::InstanceSpecific& instance))
336 : fetcher_config_(fetcher_config), instance_(instance) {}
337
Name() const338 std::string Name() const override { return "SuperImageRebuilderImpl"; }
339
340 private:
Dependencies() const341 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
ResultSetup()342 Result<void> ResultSetup() override {
343 if (CF_EXPECT(SuperImageNeedsRebuilding(fetcher_config_,
344 instance_.default_target_zip(),
345 instance_.system_target_zip()))) {
346 const RebuildPaths paths =
347 CF_EXPECT(GetRebuildPaths(fetcher_config_, instance_));
348 LOG(INFO) << "The super.img is being rebuilt with provided vendor and "
349 "system target files.";
350 LOG(INFO) << "Vendor target files at: " << paths.vendor_target_zip;
351 LOG(INFO) << "System target files at: " << paths.system_target_zip;
352 CF_EXPECT(RebuildSuperImage(paths));
353 LOG(INFO) << "Rebuild complete.";
354 LOG(INFO) << "Combined target files at: " << paths.combined_target_zip;
355 LOG(INFO) << "New super.img at: " << paths.super_image_output;
356 LOG(INFO) << "New vbmeta.img at: " << paths.vbmeta_image_output;
357 }
358 return {};
359 }
360
361 const FetcherConfig& fetcher_config_;
362 const CuttlefishConfig::InstanceSpecific& instance_;
363 };
364
365 } // namespace
366
SuperImageNeedsRebuilding(const FetcherConfig & fetcher_config,const std::string & default_target_zip,const std::string & system_target_zip)367 Result<bool> SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config,
368 const std::string& default_target_zip,
369 const std::string& system_target_zip) {
370 bool has_default_target_zip = false;
371 bool has_system_target_zip = false;
372 if (default_target_zip != "" && default_target_zip != "unset") {
373 has_default_target_zip = true;
374 }
375 if (system_target_zip != "" && system_target_zip != "unset") {
376 has_system_target_zip = true;
377 }
378 CF_EXPECT(has_default_target_zip == has_system_target_zip,
379 "default_target_zip and system_target_zip "
380 "flags must be specified together");
381 // at this time, both should be the same, either true or false
382 // therefore, I only check one variable
383 if (has_default_target_zip) {
384 return true;
385 }
386
387 bool has_default_build = false;
388 bool has_system_build = false;
389 for (const auto& file_iter : fetcher_config.get_cvd_files()) {
390 if (file_iter.second.source == FileSource::DEFAULT_BUILD) {
391 has_default_build = true;
392 } else if (file_iter.second.source == FileSource::SYSTEM_BUILD) {
393 has_system_build = true;
394 }
395 }
396 return has_default_build && has_system_build;
397 }
398
399 fruit::Component<fruit::Required<const FetcherConfig,
400 const CuttlefishConfig::InstanceSpecific>,
401 SuperImageRebuilder>
SuperImageRebuilderComponent()402 SuperImageRebuilderComponent() {
403 return fruit::createComponent()
404 .bind<SuperImageRebuilder, SuperImageRebuilderImpl>()
405 .addMultibinding<SetupFeature, SuperImageRebuilder>();
406 }
407
408 } // namespace cuttlefish
409