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 TargetFiles {
67 Archive vendor_zip;
68 Archive system_zip;
69 std::vector<std::string> vendor_contents;
70 std::vector<std::string> system_contents;
71 };
72
73 struct Extracted {
74 std::set<std::string> images;
75 std::vector<std::string> system_partitions;
76 };
77
FindImports(Archive * archive,const std::string & build_prop_file)78 void FindImports(Archive* archive, const std::string& build_prop_file) {
79 auto contents = archive->ExtractToMemory(build_prop_file);
80 auto lines = android::base::Split(contents, "\n");
81 for (const auto& line : lines) {
82 auto parts = android::base::Split(line, " ");
83 if (parts.size() >= 2 && parts[0] == "import") {
84 LOG(INFO) << build_prop_file << ": " << line;
85 }
86 }
87 }
88
IsTargetFilesImage(const std::string & filename)89 bool IsTargetFilesImage(const std::string& filename) {
90 return android::base::StartsWith(filename, "IMAGES/") &&
91 android::base::EndsWith(filename, ".img");
92 }
93
IsTargetFilesBuildProp(const std::string & filename)94 bool IsTargetFilesBuildProp(const std::string& filename) {
95 return android::base::EndsWith(filename, "build.prop");
96 }
97
GetPartitionNameFromPath(const std::string & path)98 Result<std::string> GetPartitionNameFromPath(const std::string& path) {
99 std::string_view result(path);
100 CF_EXPECTF(
101 android::base::ConsumePrefix(&result, "IMAGES/"),
102 "target_files filepath {} expected to be in the \"IMAGES\" directory",
103 path);
104 CF_EXPECTF(android::base::ConsumeSuffix(&result, ".img"),
105 "target_files filepath {} expected to be a \".img\" file", path);
106 return std::string(result);
107 }
108
GetTargetFiles(const std::string & vendor_zip_path,const std::string & system_zip_path)109 Result<TargetFiles> GetTargetFiles(const std::string& vendor_zip_path,
110 const std::string& system_zip_path) {
111 auto result = TargetFiles{
112 .vendor_zip = Archive(vendor_zip_path),
113 .system_zip = Archive(system_zip_path),
114 };
115 result.vendor_contents = result.vendor_zip.Contents();
116 result.system_contents = result.system_zip.Contents();
117 CF_EXPECTF(!result.vendor_contents.empty(), "Could not open {}",
118 vendor_zip_path);
119 CF_EXPECTF(!result.system_contents.empty(), "Could not open {}",
120 system_zip_path);
121 return result;
122 }
123
CombineDynamicPartitionsInfo(TargetFiles & target_files,const std::set<std::string> & extracted_images)124 Result<MiscInfo> CombineDynamicPartitionsInfo(
125 TargetFiles& target_files, const std::set<std::string>& extracted_images) {
126 CF_EXPECTF(Contains(target_files.vendor_contents, kDynamicPartitionsPath),
127 "Vendor target files zip does not contain {}",
128 kDynamicPartitionsPath);
129 CF_EXPECTF(Contains(target_files.system_contents, kDynamicPartitionsPath),
130 "System target files zip does not contain {}",
131 kDynamicPartitionsPath);
132
133 const MiscInfo vendor_dp_info = CF_EXPECT(ParseMiscInfo(
134 target_files.vendor_zip.ExtractToMemory(kDynamicPartitionsPath)));
135 const MiscInfo system_dp_info = CF_EXPECT(ParseMiscInfo(
136 target_files.system_zip.ExtractToMemory(kDynamicPartitionsPath)));
137
138 return CF_EXPECT(GetCombinedDynamicPartitions(vendor_dp_info, system_dp_info,
139 extracted_images));
140 }
141
CombineMiscInfo(TargetFiles & target_files,const std::string & misc_output_path,const std::set<std::string> & extracted_images,const std::vector<std::string> & system_partitions)142 Result<MiscInfo> CombineMiscInfo(
143 TargetFiles& target_files, const std::string& misc_output_path,
144 const std::set<std::string>& extracted_images,
145 const std::vector<std::string>& system_partitions) {
146 CF_EXPECTF(Contains(target_files.vendor_contents, kMiscInfoPath),
147 "Vendor target files zip does not contain {}", kMiscInfoPath);
148 CF_EXPECTF(Contains(target_files.system_contents, kMiscInfoPath),
149 "System target files zip does not contain {}", kMiscInfoPath);
150
151 const MiscInfo vendor_misc = CF_EXPECT(
152 ParseMiscInfo(target_files.vendor_zip.ExtractToMemory(kMiscInfoPath)));
153 const MiscInfo system_misc = CF_EXPECT(
154 ParseMiscInfo(target_files.system_zip.ExtractToMemory(kMiscInfoPath)));
155
156 const auto combined_dp_info =
157 CF_EXPECT(CombineDynamicPartitionsInfo(target_files, extracted_images));
158 const auto output_misc = CF_EXPECT(MergeMiscInfos(
159 vendor_misc, system_misc, combined_dp_info, system_partitions));
160
161 CF_EXPECT(WriteMiscInfo(output_misc, misc_output_path));
162 return std::move(output_misc);
163 }
164
ExtractTargetFiles(TargetFiles & target_files,const std::string & combined_output_path)165 Result<Extracted> ExtractTargetFiles(TargetFiles& target_files,
166 const std::string& combined_output_path) {
167 Extracted extracted;
168 for (const auto& name : target_files.vendor_contents) {
169 if (!IsTargetFilesImage(name)) {
170 continue;
171 } else if (!Contains(kVendorTargetImages, name)) {
172 continue;
173 }
174 LOG(INFO) << "Writing " << name << " from vendor target";
175 CF_EXPECT(
176 target_files.vendor_zip.ExtractFiles({name}, combined_output_path),
177 "Failed to extract " << name << " from the vendor target zip");
178 extracted.images.emplace(CF_EXPECT(GetPartitionNameFromPath(name)));
179 }
180 for (const auto& name : target_files.vendor_contents) {
181 if (!IsTargetFilesBuildProp(name)) {
182 continue;
183 } else if (!Contains(kVendorTargetBuildProps, name)) {
184 continue;
185 }
186 FindImports(&target_files.vendor_zip, name);
187 LOG(INFO) << "Writing " << name << " from vendor target";
188 CF_EXPECT(
189 target_files.vendor_zip.ExtractFiles({name}, combined_output_path),
190 "Failed to extract " << name << " from the vendor target zip");
191 }
192
193 for (const auto& name : target_files.system_contents) {
194 if (!IsTargetFilesImage(name)) {
195 continue;
196 } else if (Contains(kVendorTargetImages, name)) {
197 continue;
198 }
199 LOG(INFO) << "Writing " << name << " from system target";
200 CF_EXPECT(
201 target_files.system_zip.ExtractFiles({name}, combined_output_path),
202 "Failed to extract " << name << " from the system target zip");
203 const auto partition = CF_EXPECT(GetPartitionNameFromPath(name));
204 extracted.images.emplace(partition);
205 extracted.system_partitions.emplace_back(partition);
206 }
207 for (const auto& name : target_files.system_contents) {
208 if (!IsTargetFilesBuildProp(name)) {
209 continue;
210 } else if (Contains(kVendorTargetBuildProps, name)) {
211 continue;
212 }
213 FindImports(&target_files.system_zip, name);
214 LOG(INFO) << "Writing " << name << " from system target";
215 CF_EXPECT(
216 target_files.system_zip.ExtractFiles({name}, combined_output_path),
217 "Failed to extract " << name << " from the system target zip");
218 }
219 return extracted;
220 }
221
RegenerateVbmeta(const MiscInfo & misc_info,const std::string & output_path,const std::string & image_path)222 Result<void> RegenerateVbmeta(const MiscInfo& misc_info,
223 const std::string& output_path,
224 const std::string& image_path) {
225 const VbmetaArgs args = CF_EXPECT(GetVbmetaArgs(misc_info, image_path));
226 auto avbtool = Avb(AvbToolBinary(), args.algorithm, args.key_path);
227 CF_EXPECT(avbtool.MakeVbMetaImage(output_path, args.chained_partitions,
228 args.included_partitions,
229 args.extra_arguments));
230 return {};
231 }
232
CombineTargetZipFiles(const std::string & vendor_zip_path,const std::string & system_zip_path,const std::string & combined_target_path,const std::string & vbmeta_output_path)233 Result<void> CombineTargetZipFiles(const std::string& vendor_zip_path,
234 const std::string& system_zip_path,
235 const std::string& combined_target_path,
236 const std::string& vbmeta_output_path) {
237 CF_EXPECT(EnsureDirectoryExists(combined_target_path));
238 CF_EXPECT(EnsureDirectoryExists(combined_target_path + "/META"));
239 auto target_files =
240 CF_EXPECT(GetTargetFiles(vendor_zip_path, system_zip_path));
241 const auto extracted =
242 CF_EXPECT(ExtractTargetFiles(target_files, combined_target_path));
243 const auto misc_output_path = combined_target_path + "/" + kMiscInfoPath;
244 const auto combined_info =
245 CF_EXPECT(CombineMiscInfo(target_files, misc_output_path,
246 extracted.images, extracted.system_partitions));
247 CF_EXPECT(RegenerateVbmeta(combined_info, vbmeta_output_path,
248 combined_target_path));
249 return {};
250 }
251
BuildSuperImage(const std::string & combined_target_zip,const std::string & output_path)252 bool BuildSuperImage(const std::string& combined_target_zip,
253 const std::string& output_path) {
254 std::string otatools_path = DefaultHostArtifactsPath("");
255 std::string build_super_image_binary = HostBinaryPath("build_super_image");
256 if (!FileExists(build_super_image_binary)) {
257 LOG(ERROR) << "Could not find build_super_image";
258 return false;
259 }
260 return Execute({
261 build_super_image_binary,
262 "--path=" + otatools_path,
263 combined_target_zip,
264 output_path,
265 }) == 0;
266 }
267
TargetFilesZip(const FetcherConfig & fetcher_config,FileSource source)268 std::string TargetFilesZip(const FetcherConfig& fetcher_config,
269 FileSource source) {
270 for (const auto& file_iter : fetcher_config.get_cvd_files()) {
271 const auto& file_path = file_iter.first;
272 const auto& file_info = file_iter.second;
273 if (file_info.source != source) {
274 continue;
275 }
276 std::string expected_filename = "target_files-" + file_iter.second.build_id;
277 if (file_path.find(expected_filename) != std::string::npos) {
278 return file_path;
279 }
280 }
281 return "";
282 }
283
RebuildSuperImage(const FetcherConfig & fetcher_config,const CuttlefishConfig & config,const std::string & super_image_output,const std::string & vbmeta_image_output)284 Result<void> RebuildSuperImage(const FetcherConfig& fetcher_config,
285 const CuttlefishConfig& config,
286 const std::string& super_image_output,
287 const std::string& vbmeta_image_output) {
288 auto instance = config.ForDefaultInstance();
289 // In SuperImageNeedsRebuilding, it already checked both
290 // has_default_target_zip and has_system_target_zip are the same.
291 // Here, we only check if there is an input path
292 std::string default_target_zip = instance.default_target_zip();
293 std::string system_target_zip = instance.system_target_zip();
294 if (default_target_zip == "" || default_target_zip == "unset") {
295 default_target_zip =
296 TargetFilesZip(fetcher_config, FileSource::DEFAULT_BUILD);
297 CF_EXPECT(default_target_zip != "",
298 "Unable to find default target zip file.");
299
300 system_target_zip =
301 TargetFilesZip(fetcher_config, FileSource::SYSTEM_BUILD);
302 CF_EXPECT(system_target_zip != "", "Unable to find system target zip file.");
303 }
304
305 // TODO(schuffelen): Use cuttlefish_assembly
306 std::string combined_target_path = instance.PerInstanceInternalPath("target_combined");
307 // TODO(schuffelen): Use otatools/bin/merge_target_files
308 CF_EXPECT(CombineTargetZipFiles(default_target_zip, system_target_zip,
309 combined_target_path, vbmeta_image_output),
310 "Could not combine target zip files.");
311
312 CF_EXPECT(BuildSuperImage(combined_target_path, super_image_output),
313 "Could not write the final output super image.");
314 return {};
315 }
316
317 class SuperImageRebuilderImpl : public SuperImageRebuilder {
318 public:
INJECT(SuperImageRebuilderImpl (const FetcherConfig & fetcher_config,const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance))319 INJECT(SuperImageRebuilderImpl(
320 const FetcherConfig& fetcher_config, const CuttlefishConfig& config,
321 const CuttlefishConfig::InstanceSpecific& instance))
322 : fetcher_config_(fetcher_config), config_(config), instance_(instance) {}
323
Name() const324 std::string Name() const override { return "SuperImageRebuilderImpl"; }
Enabled() const325 bool Enabled() const override { return true; }
326
327 private:
Dependencies() const328 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
ResultSetup()329 Result<void> ResultSetup() override {
330 if (CF_EXPECT(SuperImageNeedsRebuilding(fetcher_config_,
331 instance_.default_target_zip(),
332 instance_.system_target_zip()))) {
333 CF_EXPECT(RebuildSuperImage(fetcher_config_, config_,
334 instance_.new_super_image(),
335 instance_.new_vbmeta_image()));
336 }
337 return {};
338 }
339
340 const FetcherConfig& fetcher_config_;
341 const CuttlefishConfig& config_;
342 const CuttlefishConfig::InstanceSpecific& instance_;
343 };
344
345 } // namespace
346
SuperImageNeedsRebuilding(const FetcherConfig & fetcher_config,const std::string & default_target_zip,const std::string & system_target_zip)347 Result<bool> SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config,
348 const std::string& default_target_zip,
349 const std::string& system_target_zip) {
350 bool has_default_target_zip = false;
351 bool has_system_target_zip = false;
352 if (default_target_zip != "" && default_target_zip != "unset") {
353 has_default_target_zip = true;
354 }
355 if (system_target_zip != "" && system_target_zip != "unset") {
356 has_system_target_zip = true;
357 }
358 CF_EXPECT(has_default_target_zip == has_system_target_zip,
359 "default_target_zip and system_target_zip "
360 "flags must be specified together");
361 // at this time, both should be the same, either true or false
362 // therefore, I only check one variable
363 if (has_default_target_zip) {
364 return true;
365 }
366
367 bool has_default_build = false;
368 bool has_system_build = false;
369 for (const auto& file_iter : fetcher_config.get_cvd_files()) {
370 if (file_iter.second.source == FileSource::DEFAULT_BUILD) {
371 has_default_build = true;
372 } else if (file_iter.second.source == FileSource::SYSTEM_BUILD) {
373 has_system_build = true;
374 }
375 }
376 return has_default_build && has_system_build;
377 }
378
379 fruit::Component<fruit::Required<const FetcherConfig, const CuttlefishConfig,
380 const CuttlefishConfig::InstanceSpecific>,
381 SuperImageRebuilder>
SuperImageRebuilderComponent()382 SuperImageRebuilderComponent() {
383 return fruit::createComponent()
384 .bind<SuperImageRebuilder, SuperImageRebuilderImpl>()
385 .addMultibinding<SetupFeature, SuperImageRebuilder>();
386 }
387
388 } // namespace cuttlefish
389