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