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 <iostream>
17 #include <iterator>
18 #include <string>
19
20 #include <sys/stat.h>
21 #include <unistd.h>
22
23 #include "android-base/logging.h"
24 #include "android-base/strings.h"
25 #include "gflags/gflags.h"
26
27 #include "common/libs/fs/shared_fd.h"
28 #include "common/libs/utils/archive.h"
29 #include "common/libs/utils/files.h"
30 #include "common/libs/utils/subprocess.h"
31
32 #include "host/libs/config/fetcher_config.h"
33
34 #include "build_api.h"
35 #include "credential_source.h"
36 #include "install_zip.h"
37
38 namespace {
39
40 const std::string DEFAULT_BRANCH = "aosp-master";
41 const std::string DEFAULT_BUILD_TARGET = "aosp_cf_x86_64_phone-userdebug";
42 }
43
44 using cuttlefish::CurrentDirectory;
45
46 DEFINE_string(default_build, DEFAULT_BRANCH + "/" + DEFAULT_BUILD_TARGET,
47 "source for the cuttlefish build to use (vendor.img + host)");
48 DEFINE_string(system_build, "", "source for system.img and product.img");
49 DEFINE_string(kernel_build, "", "source for the kernel or gki target");
50 DEFINE_string(bootloader_build, "", "source for the bootloader target");
51 DEFINE_string(otatools_build, "", "source for the host ota tools");
52
53 DEFINE_bool(download_img_zip, true, "Whether to fetch the -img-*.zip file.");
54 DEFINE_bool(download_target_files_zip, false, "Whether to fetch the "
55 "-target_files-*.zip file.");
56
57 DEFINE_string(credential_source, "", "Build API credential source");
58 DEFINE_string(directory, CurrentDirectory(), "Target directory to fetch "
59 "files into");
60 DEFINE_bool(run_next_stage, false, "Continue running the device through the next stage.");
61 DEFINE_string(wait_retry_period, "20", "Retry period for pending builds given "
62 "in seconds. Set to 0 to not wait.");
63
64 namespace cuttlefish {
65 namespace {
66
67 const std::string HOST_TOOLS = "cvd-host_package.tar.gz";
68 const std::string OTA_TOOLS = "otatools.zip";
69 const std::string OTA_TOOLS_DIR = "/otatools/";
70
71 /** Returns the name of one of the artifact target zip files.
72 *
73 * For example, for a target "aosp_cf_x86_phone-userdebug" at a build "5824130",
74 * the image zip file would be "aosp_cf_x86_phone-img-5824130.zip"
75 */
TargetBuildZipFromArtifacts(const Build & build,const std::string & name,const std::vector<Artifact> & artifacts)76 std::string TargetBuildZipFromArtifacts(
77 const Build& build, const std::string& name,
78 const std::vector<Artifact>& artifacts) {
79 std::string product = std::visit([](auto&& arg) { return arg.product; }, build);
80 auto id = std::visit([](auto&& arg) { return arg.id; }, build);
81 auto match = product + "-" + name + "-" + id;
82 for (const auto& artifact : artifacts) {
83 if (artifact.Name().find(match) != std::string::npos) {
84 return artifact.Name();
85 }
86 }
87 return "";
88 }
89
download_images(BuildApi * build_api,const Build & build,const std::string & target_directory,const std::vector<std::string> & images)90 std::vector<std::string> download_images(BuildApi* build_api,
91 const Build& build,
92 const std::string& target_directory,
93 const std::vector<std::string>& images) {
94 auto artifacts = build_api->Artifacts(build);
95 std::string img_zip_name = TargetBuildZipFromArtifacts(build, "img", artifacts);
96 if (img_zip_name.size() == 0) {
97 LOG(ERROR) << "Target " << build << " did not have an img zip";
98 return {};
99 }
100 std::string local_path = target_directory + "/" + img_zip_name;
101 if (!build_api->ArtifactToFile(build, img_zip_name, local_path)) {
102 LOG(ERROR) << "Unable to download " << build << ":" << img_zip_name << " to "
103 << local_path;
104 return {};
105 }
106
107 std::vector<std::string> files = ExtractImages(local_path, target_directory, images);
108 if (files.empty()) {
109 LOG(ERROR) << "Could not extract " << local_path;
110 return {};
111 }
112 if (unlink(local_path.c_str()) != 0) {
113 LOG(ERROR) << "Could not delete " << local_path;
114 files.push_back(local_path);
115 }
116 return files;
117 }
download_images(BuildApi * build_api,const Build & build,const std::string & target_directory)118 std::vector<std::string> download_images(BuildApi* build_api,
119 const Build& build,
120 const std::string& target_directory) {
121 return download_images(build_api, build, target_directory, {});
122 }
123
download_target_files(BuildApi * build_api,const Build & build,const std::string & target_directory)124 std::vector<std::string> download_target_files(BuildApi* build_api,
125 const Build& build,
126 const std::string& target_directory) {
127 auto artifacts = build_api->Artifacts(build);
128 std::string target_zip = TargetBuildZipFromArtifacts(build, "target_files", artifacts);
129 if (target_zip.size() == 0) {
130 LOG(ERROR) << "Target " << build << " did not have a target files zip";
131 return {};
132 }
133 std::string local_path = target_directory + "/" + target_zip;
134 if (!build_api->ArtifactToFile(build, target_zip, local_path)) {
135 LOG(ERROR) << "Unable to download " << build << ":" << target_zip << " to "
136 << local_path;
137 return {};
138 }
139 return {local_path};
140 }
141
download_host_package(BuildApi * build_api,const Build & build,const std::string & target_directory)142 std::vector<std::string> download_host_package(BuildApi* build_api,
143 const Build& build,
144 const std::string& target_directory) {
145 auto artifacts = build_api->Artifacts(build);
146 bool has_host_package = false;
147 for (const auto& artifact : artifacts) {
148 has_host_package |= artifact.Name() == HOST_TOOLS;
149 }
150 if (!has_host_package) {
151 LOG(ERROR) << "Target " << build << " did not have " << HOST_TOOLS;
152 return {};
153 }
154 std::string local_path = target_directory + "/" + HOST_TOOLS;
155
156 if (!build_api->ArtifactToFile(build, HOST_TOOLS, local_path)) {
157 LOG(ERROR) << "Unable to download " << build << ":" << HOST_TOOLS << " to "
158 << local_path;
159 return {};
160 }
161
162 Archive archive(local_path);
163 if (!archive.ExtractAll(target_directory)) {
164 LOG(ERROR) << "Could not extract " << local_path;
165 return {};
166 }
167 std::vector<std::string> files = archive.Contents();
168 for (auto& file : files) {
169 file = target_directory + "/" + file;
170 }
171 if (unlink(local_path.c_str()) != 0) {
172 LOG(ERROR) << "Could not delete " << local_path;
173 files.push_back(local_path);
174 }
175 return files;
176 }
177
download_ota_tools(BuildApi * build_api,const Build & build,const std::string & target_directory)178 std::vector<std::string> download_ota_tools(BuildApi* build_api,
179 const Build& build,
180 const std::string& target_directory) {
181 auto artifacts = build_api->Artifacts(build);
182 bool has_host_package = false;
183 for (const auto& artifact : artifacts) {
184 has_host_package |= artifact.Name() == OTA_TOOLS;
185 }
186 if (!has_host_package) {
187 LOG(ERROR) << "Target " << build << " did not have " << OTA_TOOLS;
188 return {};
189 }
190 std::string local_path = target_directory + "/" + OTA_TOOLS;
191
192 if (!build_api->ArtifactToFile(build, OTA_TOOLS, local_path)) {
193 LOG(ERROR) << "Unable to download " << build << ":" << OTA_TOOLS << " to "
194 << local_path;
195 return {};
196 }
197
198 std::string otatools_dir = target_directory + OTA_TOOLS_DIR;
199 if (!DirectoryExists(otatools_dir) && mkdir(otatools_dir.c_str(), 0777) != 0) {
200 LOG(ERROR) << "Could not create " << otatools_dir;
201 return {};
202 }
203 Archive archive(local_path);
204 if (!archive.ExtractAll(otatools_dir)) {
205 LOG(ERROR) << "Could not extract " << local_path;
206 return {};
207 }
208 std::vector<std::string> files = archive.Contents();
209 for (auto& file : files) {
210 file = target_directory + OTA_TOOLS_DIR + file;
211 }
212 files.push_back(local_path);
213 return files;
214 }
215
AddFilesToConfig(FileSource purpose,const Build & build,const std::vector<std::string> & paths,FetcherConfig * config,const std::string & directory_prefix,bool override_entry=false)216 void AddFilesToConfig(FileSource purpose, const Build& build,
217 const std::vector<std::string>& paths,
218 FetcherConfig* config,
219 const std::string& directory_prefix,
220 bool override_entry = false) {
221 for (const std::string& path : paths) {
222 std::string_view local_path(path);
223 if (!android::base::ConsumePrefix(&local_path, directory_prefix)) {
224 LOG(ERROR) << "Failed to remove prefix " << directory_prefix << " from "
225 << local_path;
226 }
227 while (android::base::StartsWith(local_path, "/")) {
228 android::base::ConsumePrefix(&local_path, "/");
229 }
230 // TODO(schuffelen): Do better for local builds here.
231 auto id = std::visit([](auto&& arg) { return arg.id; }, build);
232 auto target = std::visit([](auto&& arg) { return arg.target; }, build);
233 CvdFile file(purpose, id, target, std::string(local_path));
234 bool added = config->add_cvd_file(file, override_entry);
235 if (!added) {
236 LOG(ERROR) << "Duplicate file " << file;
237 LOG(ERROR) << "Existing file: " << config->get_cvd_files()[path];
238 LOG(FATAL) << "Failed to add path " << path;
239 }
240 }
241 }
242
243 std::string USAGE_MESSAGE =
244 "<flags>\n"
245 "\n"
246 "\"*_build\" flags accept values in the following format:\n"
247 "\"branch/build_target\" - latest build of \"branch\" for \"build_target\"\n"
248 "\"build_id/build_target\" - build \"build_id\" for \"build_target\"\n"
249 "\"branch\" - latest build of \"branch\" for \"aosp_cf_x86_phone-userdebug\"\n"
250 "\"build_id\" - build \"build_id\" for \"aosp_cf_x86_phone-userdebug\"\n";
251
252 } // namespace
253
FetchCvdMain(int argc,char ** argv)254 int FetchCvdMain(int argc, char** argv) {
255 ::android::base::InitLogging(argv, android::base::StderrLogger);
256 gflags::SetUsageMessage(USAGE_MESSAGE);
257 gflags::ParseCommandLineFlags(&argc, &argv, true);
258
259 FetcherConfig config;
260 config.RecordFlags();
261
262 std::string target_dir = AbsolutePath(FLAGS_directory);
263 if (!DirectoryExists(target_dir) && mkdir(target_dir.c_str(), 0777) != 0) {
264 LOG(FATAL) << "Could not create " << target_dir;
265 }
266 std::string target_dir_slash = target_dir;
267 std::chrono::seconds retry_period(std::stoi(FLAGS_wait_retry_period));
268
269 curl_global_init(CURL_GLOBAL_DEFAULT);
270 {
271 std::unique_ptr<CredentialSource> credential_source;
272 if (FLAGS_credential_source == "gce") {
273 credential_source = GceMetadataCredentialSource::make();
274 } else if (FLAGS_credential_source != "") {
275 credential_source = FixedCredentialSource::make(FLAGS_credential_source);
276 }
277 BuildApi build_api(std::move(credential_source));
278
279 auto default_build = ArgumentToBuild(&build_api, FLAGS_default_build,
280 DEFAULT_BUILD_TARGET,
281 retry_period);
282
283 std::vector<std::string> host_package_files =
284 download_host_package(&build_api, default_build, target_dir);
285 if (host_package_files.empty()) {
286 LOG(FATAL) << "Could not download host package for " << default_build;
287 }
288 AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build,
289 host_package_files, &config, target_dir);
290
291 if (FLAGS_system_build != "" || FLAGS_kernel_build != "" || FLAGS_otatools_build != "") {
292 auto ota_build = default_build;
293 if (FLAGS_otatools_build != "") {
294 ota_build = ArgumentToBuild(&build_api, FLAGS_otatools_build,
295 DEFAULT_BUILD_TARGET, retry_period);
296 }
297 std::vector<std::string> ota_tools_files =
298 download_ota_tools(&build_api, ota_build, target_dir);
299 if (ota_tools_files.empty()) {
300 LOG(FATAL) << "Could not download ota tools for " << ota_build;
301 }
302 AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build,
303 ota_tools_files, &config, target_dir);
304 }
305 if (FLAGS_download_img_zip) {
306 std::vector<std::string> image_files =
307 download_images(&build_api, default_build, target_dir);
308 if (image_files.empty()) {
309 LOG(FATAL) << "Could not download images for " << default_build;
310 }
311 LOG(INFO) << "Adding img-zip files for default build";
312 for (auto& file : image_files) {
313 LOG(INFO) << file;
314 }
315 AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build, image_files,
316 &config, target_dir);
317 }
318 if (FLAGS_system_build != "" || FLAGS_download_target_files_zip) {
319 std::string default_target_dir = target_dir + "/default";
320 if (mkdir(default_target_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
321 LOG(FATAL) << "Could not create " << default_target_dir;
322 }
323 std::vector<std::string> target_files =
324 download_target_files(&build_api, default_build, default_target_dir);
325 if (target_files.empty()) {
326 LOG(FATAL) << "Could not download target files for " << default_build;
327 }
328 LOG(INFO) << "Adding target files for default build";
329 AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build, target_files,
330 &config, target_dir);
331 }
332
333 if (FLAGS_system_build != "") {
334 auto system_build = ArgumentToBuild(&build_api, FLAGS_system_build,
335 DEFAULT_BUILD_TARGET,
336 retry_period);
337 bool system_in_img_zip = true;
338 if (FLAGS_download_img_zip) {
339 std::vector<std::string> image_files =
340 download_images(&build_api, system_build, target_dir,
341 {"system.img", "product.img"});
342 if (image_files.empty()) {
343 LOG(INFO) << "Could not find system image for " << system_build
344 << "in the img zip. Assuming a super image build, which will "
345 << "get the system image from the target zip.";
346 system_in_img_zip = false;
347 } else {
348 LOG(INFO) << "Adding img-zip files for system build";
349 AddFilesToConfig(FileSource::SYSTEM_BUILD, system_build, image_files,
350 &config, target_dir, true);
351 }
352 }
353 std::string system_target_dir = target_dir + "/system";
354 if (mkdir(system_target_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
355 LOG(FATAL) << "Could not create " << system_target_dir;
356 }
357 std::vector<std::string> target_files =
358 download_target_files(&build_api, system_build, system_target_dir);
359 if (target_files.empty()) {
360 LOG(FATAL) << "Could not download target files for " << system_build;
361 return -1;
362 }
363 AddFilesToConfig(FileSource::SYSTEM_BUILD, system_build, target_files,
364 &config, target_dir);
365 if (!system_in_img_zip) {
366 if (ExtractImages(target_files[0], target_dir, {"IMAGES/system.img"})
367 != std::vector<std::string>{}) {
368 std::string extracted_system = target_dir + "/IMAGES/system.img";
369 std::string target_system = target_dir + "/system.img";
370 if (rename(extracted_system.c_str(), target_system.c_str())) {
371 int error_num = errno;
372 LOG(FATAL) << "Could not replace system.img in target directory: "
373 << strerror(error_num);
374 return -1;
375 }
376 } else {
377 LOG(FATAL) << "Could not get system.img from the target zip";
378 return -1;
379 }
380 if (ExtractImages(target_files[0], target_dir, {"IMAGES/product.img"})
381 != std::vector<std::string>{}) {
382 std::string extracted_product = target_dir + "/IMAGES/product.img";
383 std::string target_product = target_dir + "/product.img";
384 if (rename(extracted_product.c_str(), target_product.c_str())) {
385 int error_num = errno;
386 LOG(FATAL) << "Could not replace product.img in target directory"
387 << strerror(error_num);
388 return -1;
389 }
390 }
391 if (ExtractImages(target_files[0], target_dir, {"IMAGES/system_ext.img"})
392 != std::vector<std::string>{}) {
393 std::string extracted_system_ext = target_dir + "/IMAGES/system_ext.img";
394 std::string target_system_ext = target_dir + "/system_ext.img";
395 if (rename(extracted_system_ext.c_str(), target_system_ext.c_str())) {
396 int error_num = errno;
397 LOG(FATAL) << "Could not move system_ext.img in target directory: "
398 << strerror(error_num);
399 return -1;
400 }
401 }
402 if (ExtractImages(target_files[0], target_dir, {"IMAGES/vbmeta_system.img"})
403 != std::vector<std::string>{}) {
404 std::string extracted_vbmeta_system = target_dir + "/IMAGES/vbmeta_system.img";
405 std::string target_vbmeta_system = target_dir + "/vbmeta_system.img";
406 if (rename(extracted_vbmeta_system.c_str(), target_vbmeta_system.c_str())) {
407 int error_num = errno;
408 LOG(FATAL) << "Could not move vbmeta_system.img in target directory: "
409 << strerror(error_num);
410 return -1;
411 }
412 }
413 // This should technically call AddFilesToConfig with the produced files,
414 // but it will conflict with the ones produced from the default system image
415 // and pie doesn't care about the produced file list anyway.
416 }
417 }
418
419 if (FLAGS_kernel_build != "") {
420 auto kernel_build = ArgumentToBuild(&build_api, FLAGS_kernel_build,
421 "kernel", retry_period);
422
423 std::string local_path = target_dir + "/kernel";
424 if (build_api.ArtifactToFile(kernel_build, "bzImage", local_path)) {
425 AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_build, {local_path},
426 &config, target_dir);
427 }
428 // If the kernel is from an arm/aarch64 build, the artifact will be called
429 // Image.
430 else if (build_api.ArtifactToFile(kernel_build, "Image", local_path)) {
431 AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_build, {local_path},
432 &config, target_dir);
433 } else {
434 LOG(FATAL) << "Could not download " << kernel_build << ":bzImage to "
435 << local_path;
436 }
437 std::vector<Artifact> kernel_artifacts = build_api.Artifacts(kernel_build);
438 for (const auto& artifact : kernel_artifacts) {
439 if (artifact.Name() != "initramfs.img") {
440 continue;
441 }
442 bool downloaded = build_api.ArtifactToFile(
443 kernel_build, "initramfs.img", target_dir + "/initramfs.img");
444 if (!downloaded) {
445 LOG(FATAL) << "Could not download " << kernel_build << ":initramfs.img to "
446 << target_dir + "/initramfs.img";
447 }
448 AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_build,
449 {target_dir + "/initramfs.img"}, &config, target_dir);
450 }
451 }
452
453 if (FLAGS_bootloader_build != "") {
454 auto bootloader_build = ArgumentToBuild(&build_api,
455 FLAGS_bootloader_build,
456 "u-boot_crosvm_x86_64",
457 retry_period);
458
459 std::string local_path = target_dir + "/bootloader";
460 if (build_api.ArtifactToFile(bootloader_build, "u-boot.rom", local_path)) {
461 AddFilesToConfig(FileSource::BOOTLOADER_BUILD, bootloader_build,
462 {local_path}, &config, target_dir, true);
463 }
464 // If the bootloader is from an arm/aarch64 build, the artifact will be of
465 // filetype bin.
466 else if (build_api.ArtifactToFile(bootloader_build, "u-boot.bin",
467 local_path)) {
468 AddFilesToConfig(FileSource::BOOTLOADER_BUILD, bootloader_build,
469 {local_path}, &config, target_dir, true);
470 } else {
471 LOG(FATAL) << "Could not download " << bootloader_build << ":u-boot.rom to "
472 << local_path;
473 }
474 }
475 }
476 curl_global_cleanup();
477
478 // Due to constraints of the build system, artifacts intentionally cannot determine
479 // their own build id. So it's unclear which build number fetch_cvd itself was built at.
480 // https://android.googlesource.com/platform/build/+/979c9f3/Changes.md#build_number
481 std::string fetcher_path = target_dir + "/fetcher_config.json";
482 AddFilesToConfig(GENERATED, DeviceBuild("", ""), {fetcher_path}, &config,
483 target_dir);
484 config.SaveToFile(fetcher_path);
485
486 for (const auto& file : config.get_cvd_files()) {
487 std::cout << target_dir << "/" << file.second.file_path << "\n";
488 }
489 std::cout << std::flush;
490
491 if (!FLAGS_run_next_stage) {
492 return 0;
493 }
494
495 // Ignore return code. We want to make sure there is no running instance,
496 // and stop_cvd will exit with an error code if there is already no running instance.
497 Command stop_cmd(target_dir + "/bin/stop_cvd");
498 stop_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
499 Subprocess::StdIOChannel::kStdErr);
500 stop_cmd.Start().Wait();
501
502 // gflags::ParseCommandLineFlags will remove fetch_cvd's flags from this.
503 // This depends the remove_flags argument (3rd) is "true".
504
505 auto filelist_fd = SharedFD::MemfdCreate("files_list");
506 if (!filelist_fd->IsOpen()) {
507 LOG(FATAL) << "Unable to create temp file to write file list. "
508 << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
509 }
510
511 for (const auto& file : config.get_cvd_files()) {
512 std::string file_entry = file.second.file_path + "\n";
513 auto chars_written = filelist_fd->Write(file_entry.c_str(), file_entry.size());
514 if (chars_written != file_entry.size()) {
515 LOG(FATAL) << "Unable to write entry to file list. Expected to write "
516 << file_entry.size() << " but wrote " << chars_written << ". "
517 << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
518 }
519 }
520 auto seek_result = filelist_fd->LSeek(0, SEEK_SET);
521 if (seek_result != 0) {
522 LOG(FATAL) << "Unable to seek on file list file. Expected 0, received " << seek_result
523 << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
524 }
525
526 if (filelist_fd->UNMANAGED_Dup2(0) == -1) {
527 LOG(FATAL) << "Unable to set file list to stdin. "
528 << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
529 }
530
531 // TODO(b/139199114): Go into assemble_cvd when the interface is stable and implemented.
532
533 std::string next_stage = target_dir + "/bin/launch_cvd";
534 std::vector<const char*> next_stage_argv = {"launch_cvd"};
535 LOG(INFO) << "Running " << next_stage;
536 for (int i = 1; i < argc; i++) {
537 LOG(INFO) << argv[i];
538 next_stage_argv.push_back(argv[i]);
539 }
540 next_stage_argv.push_back(nullptr);
541 execv(next_stage.c_str(), const_cast<char* const*>(next_stage_argv.data()));
542 int error = errno;
543 LOG(FATAL) << "execv returned with errno " << error << ":" << strerror(error);
544
545 return -1;
546 }
547
548 } // namespace cuttlefish
549
main(int argc,char ** argv)550 int main(int argc, char** argv) {
551 return cuttlefish::FetchCvdMain(argc, argv);
552 }
553