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