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 "super_image_mixer.h"
17 
18 #include <errno.h>
19 #include <sys/stat.h>
20 
21 #include <algorithm>
22 #include <cstdio>
23 #include <functional>
24 #include <memory>
25 
26 #include <android-base/strings.h>
27 #include <android-base/logging.h>
28 
29 #include "common/libs/fs/shared_buf.h"
30 #include "common/libs/utils/archive.h"
31 #include "common/libs/utils/files.h"
32 #include "common/libs/utils/subprocess.h"
33 #include "host/commands/assemble_cvd/misc_info.h"
34 #include "host/libs/config/cuttlefish_config.h"
35 #include "host/libs/config/fetcher_config.h"
36 
37 namespace cuttlefish {
38 
SuperImageNeedsRebuilding(const FetcherConfig & fetcher_config)39 bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config) {
40   bool has_default_build = false;
41   bool has_system_build = false;
42   for (const auto& file_iter : fetcher_config.get_cvd_files()) {
43     if (file_iter.second.source == FileSource::DEFAULT_BUILD) {
44       has_default_build = true;
45     } else if (file_iter.second.source == FileSource::SYSTEM_BUILD) {
46       has_system_build = true;
47     }
48   }
49   return has_default_build && has_system_build;
50 }
51 
52 namespace {
53 
TargetFilesZip(const FetcherConfig & fetcher_config,FileSource source)54 std::string TargetFilesZip(const FetcherConfig& fetcher_config,
55                            FileSource source) {
56   for (const auto& file_iter : fetcher_config.get_cvd_files()) {
57     const auto& file_path = file_iter.first;
58     const auto& file_info = file_iter.second;
59     if (file_info.source != source) {
60       continue;
61     }
62     std::string expected_filename = "target_files-" + file_iter.second.build_id;
63     if (file_path.find(expected_filename) != std::string::npos) {
64       return file_path;
65     }
66   }
67   return "";
68 }
69 
70 const std::string kMiscInfoPath = "META/misc_info.txt";
71 const std::set<std::string> kDefaultTargetImages = {
72     "IMAGES/boot.img",        "IMAGES/init_boot.img",
73     "IMAGES/odm.img",         "IMAGES/odm_dlkm.img",
74     "IMAGES/recovery.img",    "IMAGES/userdata.img",
75     "IMAGES/vbmeta.img",      "IMAGES/vendor.img",
76     "IMAGES/vendor_dlkm.img", "IMAGES/vbmeta_vendor_dlkm.img",
77     "IMAGES/system_dlkm.img",
78 };
79 const std::set<std::string> kDefaultTargetBuildProp = {
80   "ODM/build.prop",
81   "ODM/etc/build.prop",
82   "VENDOR/build.prop",
83   "VENDOR/etc/build.prop",
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(INFO) << build_prop_file << ": " << line;
93     }
94   }
95 }
96 
CombineTargetZipFiles(const std::string & default_target_zip,const std::string & system_target_zip,const std::string & output_path)97 Result<void> CombineTargetZipFiles(const std::string& default_target_zip,
98                                    const std::string& system_target_zip,
99                                    const std::string& output_path) {
100   Archive default_target_archive(default_target_zip);
101   auto default_target_contents = default_target_archive.Contents();
102   CF_EXPECT(default_target_contents.size() != 0,
103             "Could not open " << default_target_zip);
104 
105   Archive system_target_archive(system_target_zip);
106   auto system_target_contents = system_target_archive.Contents();
107   CF_EXPECT(system_target_contents.size() != 0,
108             "Could not open " << system_target_zip);
109 
110   CF_EXPECT(
111       mkdir(output_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) >= 0,
112       "Could not create directory " << output_path);
113 
114   std::string output_meta = output_path + "/META";
115   CF_EXPECT(
116       mkdir(output_meta.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) >= 0,
117       "Could not create directory " << output_meta);
118 
119   CF_EXPECT(
120       std::find(default_target_contents.begin(), default_target_contents.end(),
121                 kMiscInfoPath) != default_target_contents.end(),
122       "Default target files zip does not have " << kMiscInfoPath);
123 
124   CF_EXPECT(
125       std::find(system_target_contents.begin(), system_target_contents.end(),
126                 kMiscInfoPath) != system_target_contents.end(),
127       "System target files zip does not have " << kMiscInfoPath);
128 
129   const auto default_misc =
130       ParseMiscInfo(default_target_archive.ExtractToMemory(kMiscInfoPath));
131   CF_EXPECT(default_misc.size() != 0,
132             "Could not read the default misc_info.txt file.");
133 
134   const auto system_misc =
135       ParseMiscInfo(system_target_archive.ExtractToMemory(kMiscInfoPath));
136   CF_EXPECT(system_misc.size() != 0,
137             "Could not read the system misc_info.txt file.");
138 
139   auto output_misc = default_misc;
140   auto system_super_partitions = SuperPartitionComponents(system_misc);
141   // Ensure specific skipped partitions end up in the misc_info.txt
142   for (auto partition :
143        {"odm", "odm_dlkm", "vendor", "vendor_dlkm", "system_dlkm"}) {
144     if (std::find(system_super_partitions.begin(), system_super_partitions.end(),
145                   partition) == system_super_partitions.end()) {
146       system_super_partitions.push_back(partition);
147     }
148   }
149   CF_EXPECT(SetSuperPartitionComponents(system_super_partitions, &output_misc),
150             "Failed to update super partitions components for misc_info");
151 
152   auto misc_output_path = output_path + "/" + kMiscInfoPath;
153   SharedFD misc_output_file =
154       SharedFD::Creat(misc_output_path.c_str(), 0644);
155   CF_EXPECT(misc_output_file->IsOpen(), "Failed to open output misc file: "
156                                             << misc_output_file->StrError());
157 
158   CF_EXPECT(WriteAll(misc_output_file, WriteMiscInfo(output_misc)) >= 0,
159             "Failed to write output misc file contents: "
160                 << misc_output_file->StrError());
161 
162   for (const auto& name : default_target_contents) {
163     if (!android::base::StartsWith(name, "IMAGES/")) {
164       continue;
165     } else if (!android::base::EndsWith(name, ".img")) {
166       continue;
167     } else if (kDefaultTargetImages.count(name) == 0) {
168       continue;
169     }
170     LOG(INFO) << "Writing " << name;
171     CF_EXPECT(default_target_archive.ExtractFiles({name}, output_path),
172               "Failed to extract " << name << " from the default target zip");
173   }
174   for (const auto& name : default_target_contents) {
175     if (!android::base::EndsWith(name, "build.prop")) {
176       continue;
177     } else if (kDefaultTargetBuildProp.count(name) == 0) {
178       continue;
179     }
180     FindImports(&default_target_archive, name);
181     LOG(INFO) << "Writing " << name;
182     CF_EXPECT(default_target_archive.ExtractFiles({name}, output_path),
183               "Failed to extract " << name << " from the default target zip");
184   }
185 
186   for (const auto& name : system_target_contents) {
187     if (!android::base::StartsWith(name, "IMAGES/")) {
188       continue;
189     } else if (!android::base::EndsWith(name, ".img")) {
190       continue;
191     } else if (kDefaultTargetImages.count(name) > 0) {
192       continue;
193     }
194     LOG(INFO) << "Writing " << name;
195     CF_EXPECT(system_target_archive.ExtractFiles({name}, output_path),
196               "Failed to extract " << name << " from the system target zip");
197   }
198   for (const auto& name : system_target_contents) {
199     if (!android::base::EndsWith(name, "build.prop")) {
200       continue;
201     } else if (kDefaultTargetBuildProp.count(name) > 0) {
202       continue;
203     }
204     FindImports(&system_target_archive, name);
205     LOG(INFO) << "Writing " << name;
206     CF_EXPECT(system_target_archive.ExtractFiles({name}, output_path),
207               "Failed to extract " << name << " from the default target zip");
208   }
209 
210   return {};
211 }
212 
BuildSuperImage(const std::string & combined_target_zip,const std::string & output_path)213 bool BuildSuperImage(const std::string& combined_target_zip,
214                      const std::string& output_path) {
215   std::string build_super_image_binary;
216   std::string otatools_path;
217   if (FileExists(DefaultHostArtifactsPath("otatools/bin/build_super_image"))) {
218     build_super_image_binary =
219         DefaultHostArtifactsPath("otatools/bin/build_super_image");
220     otatools_path = DefaultHostArtifactsPath("otatools");
221   } else if (FileExists(HostBinaryPath("build_super_image"))) {
222     build_super_image_binary =
223         HostBinaryPath("build_super_image");
224     otatools_path = DefaultHostArtifactsPath("");
225   } else {
226     LOG(ERROR) << "Could not find otatools";
227     return false;
228   }
229   return execute({
230     build_super_image_binary,
231     "--path=" + otatools_path,
232     combined_target_zip,
233     output_path,
234   }) == 0;
235 }
236 
RebuildSuperImage(const FetcherConfig & fetcher_config,const CuttlefishConfig & config,const std::string & output_path)237 Result<void> RebuildSuperImage(const FetcherConfig& fetcher_config,
238                                const CuttlefishConfig& config,
239                                const std::string& output_path) {
240   std::string default_target_zip =
241       TargetFilesZip(fetcher_config, FileSource::DEFAULT_BUILD);
242   CF_EXPECT(default_target_zip != "",
243             "Unable to find default target zip file.");
244 
245   std::string system_target_zip =
246       TargetFilesZip(fetcher_config, FileSource::SYSTEM_BUILD);
247   CF_EXPECT(system_target_zip != "", "Unable to find system target zip file.");
248 
249   auto instance = config.ForDefaultInstance();
250   // TODO(schuffelen): Use cuttlefish_assembly
251   std::string combined_target_path = instance.PerInstanceInternalPath("target_combined");
252   // TODO(schuffelen): Use otatools/bin/merge_target_files
253   CF_EXPECT(CombineTargetZipFiles(default_target_zip, system_target_zip,
254                                   combined_target_path),
255             "Could not combine target zip files.");
256 
257   CF_EXPECT(BuildSuperImage(combined_target_path, output_path),
258             "Could not write the final output super image.");
259   return {};
260 }
261 
262 class SuperImageRebuilderImpl : public SuperImageRebuilder {
263  public:
INJECT(SuperImageRebuilderImpl (const FetcherConfig & fetcher_config,const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance))264   INJECT(SuperImageRebuilderImpl(
265       const FetcherConfig& fetcher_config, const CuttlefishConfig& config,
266       const CuttlefishConfig::InstanceSpecific& instance))
267       : fetcher_config_(fetcher_config), config_(config), instance_(instance) {}
268 
Name() const269   std::string Name() const override { return "SuperImageRebuilderImpl"; }
Enabled() const270   bool Enabled() const override { return true; }
271 
272  private:
Dependencies() const273   std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
ResultSetup()274   Result<void> ResultSetup() override {
275     if (SuperImageNeedsRebuilding(fetcher_config_)) {
276       CF_EXPECT(RebuildSuperImage(fetcher_config_, config_,
277                                   instance_.new_super_image()));
278     }
279     return {};
280   }
281 
282   const FetcherConfig& fetcher_config_;
283   const CuttlefishConfig& config_;
284   const CuttlefishConfig::InstanceSpecific& instance_;
285 };
286 
287 }  // namespace
288 
289 fruit::Component<fruit::Required<const FetcherConfig, const CuttlefishConfig,
290                                  const CuttlefishConfig::InstanceSpecific>,
291                  SuperImageRebuilder>
SuperImageRebuilderComponent()292 SuperImageRebuilderComponent() {
293   return fruit::createComponent()
294       .bind<SuperImageRebuilder, SuperImageRebuilderImpl>()
295       .addMultibinding<SetupFeature, SuperImageRebuilder>();
296 }
297 
298 } // namespace cuttlefish
299