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 {
38 
39 using cuttlefish::FileExists;
40 using cuttlefish::DefaultHostArtifactsPath;
41 
TargetFilesZip(const cuttlefish::FetcherConfig & fetcher_config,cuttlefish::FileSource source)42 std::string TargetFilesZip(const cuttlefish::FetcherConfig& fetcher_config,
43                            cuttlefish::FileSource source) {
44   for (const auto& file_iter : fetcher_config.get_cvd_files()) {
45     const auto& file_path = file_iter.first;
46     const auto& file_info = file_iter.second;
47     if (file_info.source != source) {
48       continue;
49     }
50     std::string expected_filename = "target_files-" + file_iter.second.build_id;
51     if (file_path.find(expected_filename) != std::string::npos) {
52       return file_path;
53     }
54   }
55   return "";
56 }
57 
58 const std::string kMiscInfoPath = "META/misc_info.txt";
59 const std::set<std::string> kDefaultTargetImages = {
60   "IMAGES/boot.img",
61   "IMAGES/cache.img",
62   "IMAGES/odm.img",
63   "IMAGES/recovery.img",
64   "IMAGES/userdata.img",
65   "IMAGES/vbmeta.img",
66   "IMAGES/vendor.img",
67 };
68 const std::set<std::string> kDefaultTargetBuildProp = {
69   "ODM/etc/build.prop",
70   "VENDOR/build.prop",
71 };
72 
FindImports(cuttlefish::Archive * archive,const std::string & build_prop_file)73 void FindImports(cuttlefish::Archive* archive, const std::string& build_prop_file) {
74   auto contents = archive->ExtractToMemory(build_prop_file);
75   auto lines = android::base::Split(contents, "\n");
76   for (const auto& line : lines) {
77     auto parts = android::base::Split(line, " ");
78     if (parts.size() >= 2 && parts[0] == "import") {
79       LOG(INFO) << build_prop_file << ": " << line;
80     }
81   }
82 }
83 
CombineTargetZipFiles(const std::string & default_target_zip,const std::string & system_target_zip,const std::string & output_path)84 bool CombineTargetZipFiles(const std::string& default_target_zip,
85                            const std::string& system_target_zip,
86                            const std::string& output_path) {
87   cuttlefish::Archive default_target_archive(default_target_zip);
88   cuttlefish::Archive system_target_archive(system_target_zip);
89 
90   auto default_target_contents = default_target_archive.Contents();
91   if (default_target_contents.size() == 0) {
92     LOG(ERROR) << "Could not open " << default_target_zip;
93     return false;
94   }
95   auto system_target_contents = system_target_archive.Contents();
96   if (system_target_contents.size() == 0) {
97     LOG(ERROR) << "Could not open " << system_target_zip;
98     return false;
99   }
100   if (mkdir(output_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
101     LOG(ERROR) << "Could not create directory " << output_path;
102     return false;
103   }
104   std::string output_meta = output_path + "/META";
105   if (mkdir(output_meta.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
106     LOG(ERROR) << "Could not create directory " << output_meta;
107     return false;
108   }
109 
110   if (std::find(default_target_contents.begin(), default_target_contents.end(), kMiscInfoPath)
111       == default_target_contents.end()) {
112     LOG(ERROR) << "Default target files zip does not have " << kMiscInfoPath;
113     return false;
114   }
115   if (std::find(system_target_contents.begin(), system_target_contents.end(), kMiscInfoPath)
116       == system_target_contents.end()) {
117     LOG(ERROR) << "System target files zip does not have " << kMiscInfoPath;
118     return false;
119   }
120   const auto default_misc =
121       ParseMiscInfo(default_target_archive.ExtractToMemory(kMiscInfoPath));
122   if (default_misc.size() == 0) {
123     LOG(ERROR) << "Could not read the default misc_info.txt file.";
124     return false;
125   }
126   const auto system_misc =
127       ParseMiscInfo(system_target_archive.ExtractToMemory(kMiscInfoPath));
128   if (system_misc.size() == 0) {
129     LOG(ERROR) << "Could not read the system misc_info.txt file.";
130     return false;
131   }
132   auto output_misc = default_misc;
133   auto system_super_partitions = SuperPartitionComponents(system_misc);
134   if (std::find(system_super_partitions.begin(), system_super_partitions.end(),
135                 "odm") == system_super_partitions.end()) {
136     // odm is not one of the partitions skipped by the system check
137     system_super_partitions.push_back("odm");
138   }
139   if (std::find(system_super_partitions.begin(), system_super_partitions.end(),
140                 "vendor") == system_super_partitions.end()) {
141     // vendor is always required, but may be missing from the system partitions
142     system_super_partitions.push_back("vendor");
143   }
144   SetSuperPartitionComponents(system_super_partitions, &output_misc);
145   auto misc_output_path = output_path + "/" + kMiscInfoPath;
146   cuttlefish::SharedFD misc_output_file =
147       cuttlefish::SharedFD::Creat(misc_output_path.c_str(), 0644);
148   if (!misc_output_file->IsOpen()) {
149     LOG(ERROR) << "Failed to open output misc file: "
150                << misc_output_file->StrError();
151     return false;
152   }
153   if (cuttlefish::WriteAll(misc_output_file, WriteMiscInfo(output_misc)) < 0) {
154     LOG(ERROR) << "Failed to write output misc file contents: "
155                << misc_output_file->StrError();
156     return false;
157   }
158 
159   for (const auto& name : default_target_contents) {
160     if (!android::base::StartsWith(name, "IMAGES/")) {
161       continue;
162     } else if (!android::base::EndsWith(name, ".img")) {
163       continue;
164     } else if (kDefaultTargetImages.count(name) == 0) {
165       continue;
166     }
167     LOG(INFO) << "Writing " << name;
168     if (!default_target_archive.ExtractFiles({name}, output_path)) {
169       LOG(ERROR) << "Failed to extract " << name << " from the default target zip";
170       return false;
171     }
172   }
173   for (const auto& name : default_target_contents) {
174     if (!android::base::EndsWith(name, "build.prop")) {
175       continue;
176     } else if (kDefaultTargetBuildProp.count(name) == 0) {
177       continue;
178     }
179     FindImports(&default_target_archive, name);
180     LOG(INFO) << "Writing " << name;
181     if (!default_target_archive.ExtractFiles({name}, output_path)) {
182       LOG(ERROR) << "Failed to extract " << name << " from the default target zip";
183       return false;
184     }
185     auto name_parts = android::base::Split(name, "/");
186     if (name_parts.size() < 2) {
187       LOG(WARNING) << name << " does not appear to have a partition";
188       continue;
189     }
190     auto etc_path = output_path + "/" + name_parts[0] + "/etc";
191     LOG(INFO) << "Creating directory " << etc_path;
192     if (mkdir(etc_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
193         && errno != EEXIST) {
194       PLOG(ERROR) << "Could not mkdir " << etc_path;
195     }
196     std::string_view name_suffix(name.data(), name.size());
197     if (!android::base::ConsumePrefix(&name_suffix, name_parts[0] + "/")) {
198       LOG(ERROR) << name << " did not start with " << name_parts[0] + "/";
199       return false;
200     }
201     auto initial_path = output_path + "/" + name;
202     auto dest_path = output_path + "/" + name_parts[0] + "/etc/" +
203                      std::string(name_suffix);
204     LOG(INFO) << "Linking " << initial_path << " to " << dest_path;
205     if (link(initial_path.c_str(), dest_path.c_str())) {
206       PLOG(ERROR) << "Could not link " << initial_path << " to " << dest_path;
207       return false;
208     }
209   }
210 
211   for (const auto& name : system_target_contents) {
212     if (!android::base::StartsWith(name, "IMAGES/")) {
213       continue;
214     } else if (!android::base::EndsWith(name, ".img")) {
215       continue;
216     } else if (kDefaultTargetImages.count(name) > 0) {
217       continue;
218     }
219     LOG(INFO) << "Writing " << name;
220     if (!system_target_archive.ExtractFiles({name}, output_path)) {
221       LOG(ERROR) << "Failed to extract " << name << " from the system target zip";
222       return false;
223     }
224   }
225   for (const auto& name : system_target_contents) {
226     if (!android::base::EndsWith(name, "build.prop")) {
227       continue;
228     } else if (kDefaultTargetBuildProp.count(name) > 0) {
229       continue;
230     }
231     FindImports(&system_target_archive, name);
232     LOG(INFO) << "Writing " << name;
233     if (!system_target_archive.ExtractFiles({name}, output_path)) {
234       LOG(ERROR) << "Failed to extract " << name << " from the default target zip";
235       return false;
236     }
237     auto name_parts = android::base::Split(name, "/");
238     if (name_parts.size() < 2) {
239       LOG(WARNING) << name << " does not appear to have a partition";
240       continue;
241     }
242     auto etc_path = output_path + "/" + name_parts[0] + "/etc";
243     LOG(INFO) << "Creating directory " << etc_path;
244     if (mkdir(etc_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
245         && errno != EEXIST) {
246       PLOG(ERROR) << "Could not mkdir " << etc_path;
247     }
248     std::string_view name_suffix(name.data(), name.size());
249     if (!android::base::ConsumePrefix(&name_suffix, name_parts[0] + "/")) {
250       LOG(ERROR) << name << " did not start with " << name_parts[0] + "/";
251       return false;
252     }
253     auto initial_path = output_path + "/" + name;
254     auto dest_path = output_path + "/" + name_parts[0] + "/etc/" +
255                      std::string(name_suffix);
256     LOG(INFO) << "Linking " << initial_path << " to " << dest_path;
257     if (link(initial_path.c_str(), dest_path.c_str())) {
258       PLOG(ERROR) << "Could not link " << initial_path << " to " << dest_path;
259       return false;
260     }
261   }
262 
263   return true;
264 }
265 
BuildSuperImage(const std::string & combined_target_zip,const std::string & output_path)266 bool BuildSuperImage(const std::string& combined_target_zip,
267                      const std::string& output_path) {
268   std::string build_super_image_binary;
269   std::string otatools_path;
270   if (FileExists(DefaultHostArtifactsPath("otatools/bin/build_super_image"))) {
271     build_super_image_binary =
272         DefaultHostArtifactsPath("otatools/bin/build_super_image");
273     otatools_path = DefaultHostArtifactsPath("otatools");
274   } else if (FileExists(DefaultHostArtifactsPath("bin/build_super_image"))) {
275     build_super_image_binary =
276         DefaultHostArtifactsPath("bin/build_super_image");
277     otatools_path = DefaultHostArtifactsPath("");
278   } else {
279     LOG(ERROR) << "Could not find otatools";
280     return false;
281   }
282   return cuttlefish::execute({
283     build_super_image_binary,
284     "--path=" + otatools_path,
285     combined_target_zip,
286     output_path,
287   }) == 0;
288 }
289 
290 } // namespace
291 
SuperImageNeedsRebuilding(const cuttlefish::FetcherConfig & fetcher_config,const cuttlefish::CuttlefishConfig &)292 bool SuperImageNeedsRebuilding(const cuttlefish::FetcherConfig& fetcher_config,
293                                const cuttlefish::CuttlefishConfig&) {
294   bool has_default_build = false;
295   bool has_system_build = false;
296   for (const auto& file_iter : fetcher_config.get_cvd_files()) {
297     if (file_iter.second.source == cuttlefish::FileSource::DEFAULT_BUILD) {
298       has_default_build = true;
299     } else if (file_iter.second.source == cuttlefish::FileSource::SYSTEM_BUILD) {
300       has_system_build = true;
301     }
302   }
303   return has_default_build && has_system_build;
304 }
305 
RebuildSuperImage(const cuttlefish::FetcherConfig & fetcher_config,const cuttlefish::CuttlefishConfig & config,const std::string & output_path)306 bool RebuildSuperImage(const cuttlefish::FetcherConfig& fetcher_config,
307                        const cuttlefish::CuttlefishConfig& config,
308                        const std::string& output_path) {
309   std::string default_target_zip =
310       TargetFilesZip(fetcher_config, cuttlefish::FileSource::DEFAULT_BUILD);
311   if (default_target_zip == "") {
312     LOG(ERROR) << "Unable to find default target zip file.";
313     return false;
314   }
315   std::string system_target_zip =
316       TargetFilesZip(fetcher_config, cuttlefish::FileSource::SYSTEM_BUILD);
317   if (system_target_zip == "") {
318     LOG(ERROR) << "Unable to find system target zip file.";
319     return false;
320   }
321   auto instance = config.ForDefaultInstance();
322   // TODO(schuffelen): Use cuttlefish_assembly
323   std::string combined_target_path = instance.PerInstanceInternalPath("target_combined");
324   // TODO(schuffelen): Use otatools/bin/merge_target_files
325   if (!CombineTargetZipFiles(default_target_zip, system_target_zip,
326                              combined_target_path)) {
327     LOG(ERROR) << "Could not combine target zip files.";
328     return false;
329   }
330   bool success = BuildSuperImage(combined_target_path, output_path);
331   if (!success) {
332     LOG(ERROR) << "Could not write the final output super image.";
333   }
334   return success;
335 }
336