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