blob: da63aafb665aee522319b1dbb9f388d642cbf641 [file] [log] [blame]
//
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "host/commands/cvd/fetch/fetch_cvd.h"
#include <sys/stat.h>
#include <chrono>
#include <fstream>
#include <future>
#include <iostream>
#include <iterator>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <curl/curl.h>
#include <gflags/gflags.h>
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/archive.h"
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/result.h"
#include "common/libs/utils/tee_logging.h"
#include "host/libs/config/fetcher_config.h"
#include "host/libs/web/build_api.h"
#include "host/libs/web/build_string.h"
#include "host/libs/web/credential_source.h"
#include "host/libs/web/http_client/http_client.h"
namespace cuttlefish {
namespace {
constexpr char kDefaultBuildTarget[] =
"aosp_cf_x86_64_phone-trunk_staging-userdebug";
constexpr char kUsageMessage[] =
"*_build flags accept values in the following format:\n"
"{<branch> | <build_id>}[/<build_target>][{<filepath>}]\n"
"For example: "
"\"aosp-main/aosp_cf_x86_64_phone-trunk_staging-userdebug{file.txt}\""
"<branch> fetches artifacts from the latest build of the argument\n"
"{<filepath>} is used for certain artifacts to specify the file to "
"download location in the build artifacts\n"
"if <build_target> is not specified then the default build target is: ";
constexpr mode_t kRwxAllMode = S_IRWXU | S_IRWXG | S_IRWXO;
constexpr bool kOverrideEntries = true;
struct BuildApiFlags {
std::string api_key = kDefaultApiKey;
std::string credential_source = kDefaultCredentialSource;
std::chrono::seconds wait_retry_period = kDefaultWaitRetryPeriod;
bool external_dns_resolver = kDefaultExternalDnsResolver;
std::string api_base_url = kAndroidBuildServiceUrl;
};
struct VectorFlags {
std::vector<std::optional<BuildString>> default_build;
std::vector<std::optional<BuildString>> system_build;
std::vector<std::optional<BuildString>> kernel_build;
std::vector<std::optional<BuildString>> boot_build;
std::vector<std::optional<BuildString>> bootloader_build;
std::vector<std::optional<BuildString>> otatools_build;
std::vector<std::optional<BuildString>> host_package_build;
std::vector<bool> download_img_zip;
std::vector<bool> download_target_files_zip;
std::vector<std::string> boot_artifact;
};
struct FetchFlags {
std::string target_directory = kDefaultTargetDirectory;
std::vector<std::string> target_subdirectory;
bool keep_downloaded_archives = kDefaultKeepDownloadedArchives;
android::base::LogSeverity verbosity = android::base::INFO;
bool helpxml = false;
BuildApiFlags build_api_flags;
VectorFlags vector_flags;
int number_of_builds = 0;
};
struct BuildStrings {
std::optional<BuildString> default_build;
std::optional<BuildString> system_build;
std::optional<BuildString> kernel_build;
std::optional<BuildString> boot_build;
std::optional<BuildString> bootloader_build;
std::optional<BuildString> otatools_build;
std::optional<BuildString> host_package_build;
};
struct DownloadFlags {
bool download_img_zip;
bool download_target_files_zip;
};
struct TargetDirectories {
std::string root;
std::string otatools;
std::string default_target_files;
std::string system_target_files;
};
struct Target {
BuildStrings build_strings;
DownloadFlags download_flags;
TargetDirectories directories;
};
struct Builds {
std::optional<Build> default_build;
std::optional<Build> system;
std::optional<Build> kernel;
std::optional<Build> boot;
std::optional<Build> bootloader;
std::optional<Build> otatools;
Build host_package;
};
Flag GflagsCompatFlagSeconds(const std::string& name,
std::chrono::seconds& value) {
return GflagsCompatFlag(name)
.Getter([&value]() { return std::to_string(value.count()); })
.Setter([&value](const FlagMatch& match) -> Result<void> {
int parsed_int;
CF_EXPECTF(android::base::ParseInt(match.value, &parsed_int),
"Failed to parse \"{}\" as an integer", match.value);
value = std::chrono::seconds(parsed_int);
return {};
});
}
std::vector<Flag> GetFlagsVector(FetchFlags& fetch_flags,
BuildApiFlags& build_api_flags,
VectorFlags& vector_flags,
std::string& directory) {
std::vector<Flag> flags;
flags.emplace_back(
GflagsCompatFlag("directory", directory)
.Help("Target directory to fetch files into. (deprecated)"));
flags.emplace_back(
GflagsCompatFlag("target_directory", fetch_flags.target_directory)
.Help("Target directory to fetch files into."));
flags.emplace_back(GflagsCompatFlag("keep_downloaded_archives",
fetch_flags.keep_downloaded_archives)
.Help("Keep downloaded zip/tar."));
flags.emplace_back(VerbosityFlag(fetch_flags.verbosity));
flags.emplace_back(
GflagsCompatFlag("target_subdirectory", fetch_flags.target_subdirectory)
.Help("Target subdirectory to fetch files into. Specifically aimed "
"at organizing builds when there are multiple fetches. "
"**Note**: directory separator automatically prepended, only "
"give the subdirectory name."));
flags.emplace_back(GflagsCompatFlag("api_key", build_api_flags.api_key)
.Help("API key ofr the Android Build API"));
flags.emplace_back(
GflagsCompatFlag("credential_source", build_api_flags.credential_source)
.Help("Build API credential source"));
flags.emplace_back(GflagsCompatFlagSeconds("wait_retry_period",
build_api_flags.wait_retry_period)
.Help("Retry period for pending builds given in "
"seconds. Set to 0 to not wait."));
flags.emplace_back(
GflagsCompatFlag("external_dns_resolver",
build_api_flags.external_dns_resolver)
.Help("Use an out-of-process mechanism to resolve DNS queries"));
flags.emplace_back(
GflagsCompatFlag("api_base_url", build_api_flags.api_base_url)
.Help("The base url for API requests to download artifacts from"));
flags.emplace_back(
GflagsCompatFlag("default_build", vector_flags.default_build)
.Help("source for the cuttlefish build to use (vendor.img + host)"));
flags.emplace_back(GflagsCompatFlag("system_build", vector_flags.system_build)
.Help("source for system.img and product.img"));
flags.emplace_back(GflagsCompatFlag("kernel_build", vector_flags.kernel_build)
.Help("source for the kernel or gki target"));
flags.emplace_back(GflagsCompatFlag("boot_build", vector_flags.boot_build)
.Help("source for the boot or gki target"));
flags.emplace_back(
GflagsCompatFlag("bootloader_build", vector_flags.bootloader_build)
.Help("source for the bootloader target"));
flags.emplace_back(
GflagsCompatFlag("otatools_build", vector_flags.otatools_build)
.Help("source for the host ota tools"));
flags.emplace_back(
GflagsCompatFlag("host_package_build", vector_flags.host_package_build)
.Help("source for the host cvd tools"));
flags.emplace_back(
GflagsCompatFlag("boot_artifact", vector_flags.boot_artifact)
.Help("name of the boot image in boot_build"));
flags.emplace_back(GflagsCompatFlag("download_img_zip",
vector_flags.download_img_zip,
kDefaultDownloadImgZip)
.Help("Whether to fetch the -img-*.zip file."));
flags.emplace_back(
GflagsCompatFlag("download_target_files_zip",
vector_flags.download_target_files_zip,
kDefaultDownloadTargetFilesZip)
.Help("Whether to fetch the -target_files-*.zip file."));
std::stringstream help_message;
help_message << kUsageMessage << kDefaultBuildTarget;
flags.emplace_back(HelpFlag(flags, help_message.str()));
flags.emplace_back(
HelpXmlFlag(flags, std::cout, fetch_flags.helpxml, help_message.str()));
flags.emplace_back(UnexpectedArgumentGuard());
return flags;
}
Result<int> GetNumberOfBuilds(
const VectorFlags& flags,
const std::vector<std::string>& subdirectory_flag) {
std::optional<int> number_of_builds;
for (const auto& flag_size :
{flags.default_build.size(), flags.system_build.size(),
flags.kernel_build.size(), flags.boot_build.size(),
flags.bootloader_build.size(), flags.otatools_build.size(),
flags.host_package_build.size(), flags.boot_artifact.size(),
flags.download_img_zip.size(), flags.download_target_files_zip.size(),
subdirectory_flag.size()}) {
if (flag_size == 0) {
// a size zero flag vector means the flag was not given
continue;
}
if (number_of_builds) {
CF_EXPECT(
flag_size == *number_of_builds,
"Mismatched flag lengths: " << *number_of_builds << "," << flag_size);
}
number_of_builds = flag_size;
}
// if no flags had values there is 1 all-default build
return number_of_builds.value_or(1);
}
Result<FetchFlags> GetFlagValues(int argc, char** argv) {
FetchFlags fetch_flags;
BuildApiFlags build_api_flags;
VectorFlags vector_flags;
std::string directory;
std::vector<Flag> flags =
GetFlagsVector(fetch_flags, build_api_flags, vector_flags, directory);
std::vector<std::string> args = ArgsToVec(argc - 1, argv + 1);
CF_EXPECT(ParseFlags(flags, args), "Could not process command line flags.");
if (!directory.empty()) {
LOG(ERROR) << "Please use --target_directory instead of --directory";
if (fetch_flags.target_directory.empty()) {
fetch_flags.target_directory = directory;
}
} else {
if (fetch_flags.target_directory.empty()) {
fetch_flags.target_directory = CurrentDirectory();
}
}
fetch_flags.target_directory = AbsolutePath(fetch_flags.target_directory);
if (!vector_flags.boot_artifact.empty()) {
LOG(ERROR) << "Please use the build string filepath syntax instead of "
"deprecated --boot_artifact";
for (const auto& build_string : vector_flags.boot_build) {
if (build_string) {
CF_EXPECT(!GetFilepath(*build_string),
"Cannot use both the --boot_artifact flag and set the "
"filepath in the boot build string. Please use only the "
"build string filepath");
}
}
}
fetch_flags.build_api_flags = build_api_flags;
fetch_flags.vector_flags = vector_flags;
fetch_flags.number_of_builds = CF_EXPECT(GetNumberOfBuilds(
fetch_flags.vector_flags, fetch_flags.target_subdirectory));
return {fetch_flags};
}
template <typename T>
T AccessOrDefault(const std::vector<T>& vector, const int i,
const T& default_value) {
if (i < vector.size()) {
return vector[i];
} else {
return default_value;
}
}
BuildStrings GetBuildStrings(const VectorFlags& flags, const int index) {
auto build_strings = BuildStrings{
.default_build = AccessOrDefault<std::optional<BuildString>>(
flags.default_build, index, std::nullopt),
.system_build = AccessOrDefault<std::optional<BuildString>>(
flags.system_build, index, std::nullopt),
.kernel_build = AccessOrDefault<std::optional<BuildString>>(
flags.kernel_build, index, std::nullopt),
.boot_build = AccessOrDefault<std::optional<BuildString>>(
flags.boot_build, index, std::nullopt),
.bootloader_build = AccessOrDefault<std::optional<BuildString>>(
flags.bootloader_build, index, std::nullopt),
.otatools_build = AccessOrDefault<std::optional<BuildString>>(
flags.otatools_build, index, std::nullopt),
.host_package_build = AccessOrDefault<std::optional<BuildString>>(
flags.host_package_build, index, std::nullopt),
};
auto possible_boot_artifact =
AccessOrDefault<std::string>(flags.boot_artifact, index, "");
if (!possible_boot_artifact.empty() && build_strings.boot_build) {
SetFilepath(*build_strings.boot_build, possible_boot_artifact);
}
return build_strings;
}
DownloadFlags GetDownloadFlags(const VectorFlags& flags, const int index) {
return DownloadFlags{
.download_img_zip = AccessOrDefault<bool>(flags.download_img_zip, index,
kDefaultDownloadImgZip),
.download_target_files_zip =
AccessOrDefault<bool>(flags.download_target_files_zip, index,
kDefaultDownloadTargetFilesZip),
};
}
TargetDirectories GetTargetDirectories(
const std::string& target_directory,
const std::vector<std::string>& target_subdirectories, const int index,
const bool append_subdirectory) {
std::string base_directory = target_directory;
if (append_subdirectory) {
base_directory +=
"/" + AccessOrDefault<std::string>(target_subdirectories, index,
"instance_" + std::to_string(index));
}
return TargetDirectories{.root = base_directory,
.otatools = base_directory + "/otatools/",
.default_target_files = base_directory + "/default",
.system_target_files = base_directory + "/system"};
}
std::vector<Target> GetFetchTargets(const FetchFlags& flags) {
const bool append_subdirectory =
flags.number_of_builds > 1 || !flags.target_subdirectory.empty();
std::vector<Target> result(flags.number_of_builds);
for (int i = 0; i < result.size(); ++i) {
result[i] = Target{
.build_strings = GetBuildStrings(flags.vector_flags, i),
.download_flags = GetDownloadFlags(flags.vector_flags, i),
.directories = GetTargetDirectories(flags.target_directory,
flags.target_subdirectory, i,
append_subdirectory),
};
}
return result;
}
Result<void> EnsureDirectoriesExist(const std::string& target_directory,
const std::vector<Target>& targets) {
CF_EXPECT(EnsureDirectoryExists(target_directory));
for (const auto& target : targets) {
CF_EXPECT(EnsureDirectoryExists(target.directories.root, kRwxAllMode));
CF_EXPECT(EnsureDirectoryExists(target.directories.otatools, kRwxAllMode));
CF_EXPECT(EnsureDirectoryExists(target.directories.default_target_files,
kRwxAllMode));
CF_EXPECT(EnsureDirectoryExists(target.directories.system_target_files,
kRwxAllMode));
}
return {};
}
std::unique_ptr<CredentialSource> TryParseServiceAccount(
HttpClient& http_client, const std::string& file_content) {
Json::Reader reader;
Json::Value content;
if (!reader.parse(file_content, content)) {
// Don't log the actual content of the file since it could be the actual
// access token.
LOG(DEBUG) << "Could not parse credential file as Service Account";
return {};
}
auto result = ServiceAccountOauthCredentialSource::FromJson(
http_client, content, kBuildScope);
if (!result.ok()) {
LOG(DEBUG) << "Failed to load service account json file: \n"
<< result.error().FormatForEnv();
return {};
}
return std::unique_ptr<CredentialSource>(
new ServiceAccountOauthCredentialSource(std::move(*result)));
}
Result<std::vector<std::string>> ProcessHostPackage(
BuildApi& build_api, const Build& build, const std::string& target_dir,
const bool keep_archives) {
std::string host_tools_filepath = CF_EXPECT(
build_api.DownloadFile(build, target_dir, "cvd-host_package.tar.gz"));
return ExtractArchiveContents(host_tools_filepath, target_dir, keep_archives);
}
Result<std::unique_ptr<CredentialSource>> GetCredentialSource(
HttpClient& http_client, const std::string& credential_source) {
std::unique_ptr<CredentialSource> result;
if (credential_source == "gce") {
result = GceMetadataCredentialSource::Make(http_client);
} else if (credential_source == "") {
std::string file = StringFromEnv("HOME", ".") + "/.acloud_oauth2.dat";
LOG(VERBOSE) << "Probing acloud credentials at " << file;
if (FileExists(file)) {
std::ifstream stream(file);
auto attempt_load =
RefreshCredentialSource::FromOauth2ClientFile(http_client, stream);
if (attempt_load.ok()) {
result.reset(new RefreshCredentialSource(std::move(*attempt_load)));
} else {
LOG(DEBUG) << "Failed to load acloud credentials: "
<< attempt_load.error().FormatForEnv();
}
} else {
LOG(INFO) << "\"" << file << "\" missing, running without credentials";
}
} else if (!FileExists(credential_source)) {
// If the parameter doesn't point to an existing file it must be the
// credentials.
result = FixedCredentialSource::Make(credential_source);
} else {
// Read the file only once in case it's a pipe.
LOG(DEBUG) << "Attempting to open credentials file \"" << credential_source
<< "\"";
auto file = SharedFD::Open(credential_source, O_RDONLY);
CF_EXPECT(file->IsOpen(),
"Failed to open credential_source file: " << file->StrError());
std::string file_content;
auto size = ReadAll(file, &file_content);
CF_EXPECT(size >= 0,
"Failed to read credentials file: " << file->StrError());
if (auto crds = TryParseServiceAccount(http_client, file_content)) {
result = std::move(crds);
} else {
result = FixedCredentialSource::Make(file_content);
}
}
return result;
}
Result<BuildApi> GetBuildApi(const BuildApiFlags& flags) {
auto resolver =
flags.external_dns_resolver ? GetEntDnsResolve : NameResolver();
const bool use_logging_debug_function = true;
std::unique_ptr<HttpClient> curl =
HttpClient::CurlClient(resolver, use_logging_debug_function);
std::unique_ptr<HttpClient> retrying_http_client =
HttpClient::ServerErrorRetryClient(*curl, 10,
std::chrono::milliseconds(5000));
std::unique_ptr<CredentialSource> credential_source = CF_EXPECT(
GetCredentialSource(*retrying_http_client, flags.credential_source));
return BuildApi(std::move(retrying_http_client), std::move(curl),
std::move(credential_source), flags.api_key,
flags.wait_retry_period, flags.api_base_url);
}
Result<std::optional<Build>> GetBuildHelper(
BuildApi& build_api, const std::optional<BuildString>& build_source,
const std::string& fallback_target) {
if (!build_source) {
return std::nullopt;
}
return CF_EXPECT(build_api.GetBuild(*build_source, fallback_target),
"Unable to create build from ("
<< *build_source << ") and target (" << fallback_target
<< ")");
}
Result<Builds> GetBuilds(BuildApi& build_api,
const BuildStrings& build_sources) {
auto default_build = CF_EXPECT(GetBuildHelper(
build_api, build_sources.default_build, kDefaultBuildTarget));
auto host_package_build = CF_EXPECT(GetBuildHelper(
build_api, build_sources.host_package_build, kDefaultBuildTarget));
CF_EXPECT(host_package_build.has_value() || default_build.has_value(),
"Either the host_package_build or default_build requires a value. "
"(previous default_build default was "
"aosp-master/aosp_cf_x86_64_phone-userdebug)");
Builds result = Builds{
.default_build = default_build,
.system = CF_EXPECT(GetBuildHelper(build_api, build_sources.system_build,
kDefaultBuildTarget)),
.kernel = CF_EXPECT(
GetBuildHelper(build_api, build_sources.kernel_build, "kernel")),
.boot = CF_EXPECT(GetBuildHelper(build_api, build_sources.boot_build,
"gki_x86_64-user")),
.bootloader = CF_EXPECT(GetBuildHelper(
build_api, build_sources.bootloader_build, "u-boot_crosvm_x86_64")),
.otatools = CF_EXPECT(GetBuildHelper(
build_api, build_sources.otatools_build, kDefaultBuildTarget)),
.host_package = host_package_build.value_or(*default_build),
};
if (!result.otatools) {
if (result.system) {
result.otatools = result.system;
} else if (result.kernel) {
result.otatools = result.default_build;
}
}
return {result};
}
Result<void> SaveConfig(FetcherConfig& config,
const std::string& target_directory) {
// Due to constraints of the build system, artifacts intentionally cannot
// determine their own build id. So it's unclear which build number fetch_cvd
// itself was built at.
// https://android.googlesource.com/platform/build/+/979c9f3/Changes.md#build_number
std::string fetcher_path = target_directory + "/fetcher_config.json";
CF_EXPECT(config.AddFilesToConfig(FileSource::GENERATED, "", "",
{fetcher_path}, target_directory));
config.SaveToFile(fetcher_path);
for (const auto& file : config.get_cvd_files()) {
LOG(VERBOSE) << target_directory << "/" << file.second.file_path << "\n";
}
return {};
}
Result<void> FetchTarget(BuildApi& build_api, const Builds& builds,
const TargetDirectories& target_directories,
const DownloadFlags& flags,
const bool keep_downloaded_archives,
const bool is_host_package_build,
FetcherConfig& config) {
auto process_pkg_ret = std::async(
std::launch::async, ProcessHostPackage, std::ref(build_api),
std::cref(builds.host_package), std::cref(target_directories.root),
std::cref(keep_downloaded_archives));
if (builds.default_build) {
const auto [default_build_id, default_build_target] =
GetBuildIdAndTarget(*builds.default_build);
// Some older builds might not have misc_info.txt, so permit errors on
// fetching misc_info.txt
Result<std::string> misc_info_result = build_api.DownloadFile(
*builds.default_build, target_directories.root, "misc_info.txt");
if (misc_info_result.ok()) {
CF_EXPECT(config.AddFilesToConfig(
FileSource::DEFAULT_BUILD, default_build_id, default_build_target,
{misc_info_result.value()}, target_directories.root,
kOverrideEntries));
}
if (flags.download_img_zip) {
std::string img_zip_name = GetBuildZipName(*builds.default_build, "img");
std::string default_img_zip_filepath = CF_EXPECT(build_api.DownloadFile(
*builds.default_build, target_directories.root, img_zip_name));
std::vector<std::string> image_files = CF_EXPECT(ExtractArchiveContents(
default_img_zip_filepath, target_directories.root,
keep_downloaded_archives));
LOG(INFO) << "Adding img-zip files for default build";
for (auto& file : image_files) {
LOG(VERBOSE) << file;
}
CF_EXPECT(config.AddFilesToConfig(FileSource::DEFAULT_BUILD,
default_build_id, default_build_target,
image_files, target_directories.root));
}
if (builds.system || flags.download_target_files_zip) {
std::string target_files_name =
GetBuildZipName(*builds.default_build, "target_files");
std::string target_files = CF_EXPECT(build_api.DownloadFile(
*builds.default_build, target_directories.default_target_files,
target_files_name));
LOG(INFO) << "Adding target files for default build";
CF_EXPECT(config.AddFilesToConfig(
FileSource::DEFAULT_BUILD, default_build_id, default_build_target,
{target_files}, target_directories.root));
}
}
if (builds.system) {
std::string target_files_name =
GetBuildZipName(*builds.system, "target_files");
std::string target_files = CF_EXPECT(build_api.DownloadFile(
*builds.system, target_directories.system_target_files,
target_files_name));
const auto [system_id, system_target] = GetBuildIdAndTarget(*builds.system);
CF_EXPECT(config.AddFilesToConfig(FileSource::SYSTEM_BUILD, system_id,
system_target, {target_files},
target_directories.root));
if (flags.download_img_zip) {
std::string system_img_zip_name = GetBuildZipName(*builds.system, "img");
Result<std::string> system_img_zip_result = build_api.DownloadFile(
*builds.system, target_directories.root, system_img_zip_name);
Result<std::vector<std::string>> extract_result;
if (system_img_zip_result.ok()) {
extract_result = ExtractImages(
system_img_zip_result.value(), target_directories.root,
{"system.img", "product.img"}, keep_downloaded_archives);
if (extract_result.ok()) {
CF_EXPECT(config.AddFilesToConfig(
FileSource::SYSTEM_BUILD, system_id, system_target,
extract_result.value(), target_directories.root,
kOverrideEntries));
}
}
if (!system_img_zip_result.ok() || !extract_result.ok()) {
std::string extracted_system = CF_EXPECT(ExtractImage(
target_files, target_directories.root, "IMAGES/system.img"));
CF_EXPECT(RenameFile(extracted_system,
target_directories.root + "/system.img"));
Result<std::string> extracted_product_result = ExtractImage(
target_files, target_directories.root, "IMAGES/product.img");
if (extracted_product_result.ok()) {
CF_EXPECT(RenameFile(extracted_product_result.value(),
target_directories.root + "/product.img"));
}
Result<std::string> extracted_system_ext_result = ExtractImage(
target_files, target_directories.root, "IMAGES/system_ext.img");
if (extracted_system_ext_result.ok()) {
CF_EXPECT(RenameFile(extracted_system_ext_result.value(),
target_directories.root + "/system_ext.img"));
}
Result<std::string> extracted_vbmeta_system = ExtractImage(
target_files, target_directories.root, "IMAGES/vbmeta_system.img");
if (extracted_vbmeta_system.ok()) {
CF_EXPECT(RenameFile(extracted_vbmeta_system.value(),
target_directories.root + "/vbmeta_system.img"));
}
Result<std::string> extracted_init_boot = ExtractImage(
target_files, target_directories.root, "IMAGES/init_boot.img");
if (extracted_init_boot.ok()) {
CF_EXPECT(RenameFile(extracted_init_boot.value(),
target_directories.root + "/init_boot.img"));
}
}
}
}
if (builds.kernel) {
std::string kernel_filepath = target_directories.root + "/kernel";
// If the kernel is from an arm/aarch64 build, the artifact will be called
// Image.
std::string downloaded_kernel_filepath =
CF_EXPECT(build_api.DownloadFileWithBackup(
*builds.kernel, target_directories.root, "bzImage", "Image"));
RenameFile(downloaded_kernel_filepath, kernel_filepath);
const auto [kernel_id, kernel_target] = GetBuildIdAndTarget(*builds.kernel);
CF_EXPECT(config.AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_id,
kernel_target, {kernel_filepath},
target_directories.root));
// Certain kernel builds do not have corresponding ramdisks.
Result<std::string> initramfs_img_result = build_api.DownloadFile(
*builds.kernel, target_directories.root, "initramfs.img");
if (initramfs_img_result.ok()) {
CF_EXPECT(config.AddFilesToConfig(
FileSource::KERNEL_BUILD, kernel_id, kernel_target,
{initramfs_img_result.value()}, target_directories.root));
}
}
if (builds.boot) {
std::string boot_img_zip_name = GetBuildZipName(*builds.boot, "img");
std::string downloaded_boot_filepath;
std::optional<std::string> boot_filepath = GetFilepath(*builds.boot);
if (boot_filepath) {
downloaded_boot_filepath = CF_EXPECT(build_api.DownloadFileWithBackup(
*builds.boot, target_directories.root, *boot_filepath,
boot_img_zip_name));
} else {
downloaded_boot_filepath = CF_EXPECT(build_api.DownloadFile(
*builds.boot, target_directories.root, boot_img_zip_name));
}
std::vector<std::string> boot_files;
// downloaded a zip that needs to be extracted
if (android::base::EndsWith(downloaded_boot_filepath, boot_img_zip_name)) {
std::string extract_target = boot_filepath.value_or("boot.img");
std::string extracted_boot = CF_EXPECT(ExtractImage(
downloaded_boot_filepath, target_directories.root, extract_target));
std::string target_boot = CF_EXPECT(
RenameFile(extracted_boot, target_directories.root + "/boot.img"));
boot_files.push_back(target_boot);
// keep_downloaded_archives flag used because this is the last extract
// on this archive
Result<std::string> extracted_vendor_boot_result =
ExtractImage(downloaded_boot_filepath, target_directories.root,
"vendor_boot.img", keep_downloaded_archives);
if (extracted_vendor_boot_result.ok()) {
boot_files.push_back(extracted_vendor_boot_result.value());
}
} else {
boot_files.push_back(downloaded_boot_filepath);
}
const auto [boot_id, boot_target] = GetBuildIdAndTarget(*builds.boot);
CF_EXPECT(config.AddFilesToConfig(
FileSource::BOOT_BUILD, boot_id, boot_target, boot_files,
target_directories.root, kOverrideEntries));
}
if (builds.bootloader) {
std::string bootloader_filepath = target_directories.root + "/bootloader";
// If the bootloader is from an arm/aarch64 build, the artifact will be of
// filetype bin.
std::string downloaded_bootloader_filepath =
CF_EXPECT(build_api.DownloadFileWithBackup(*builds.bootloader,
target_directories.root,
"u-boot.rom", "u-boot.bin"));
RenameFile(downloaded_bootloader_filepath, bootloader_filepath);
const auto [bootloader_id, bootloader_target] =
GetBuildIdAndTarget(*builds.bootloader);
CF_EXPECT(config.AddFilesToConfig(
FileSource::BOOTLOADER_BUILD, bootloader_id, bootloader_target,
{bootloader_filepath}, target_directories.root, kOverrideEntries));
}
if (builds.otatools) {
std::string otatools_filepath = CF_EXPECT(build_api.DownloadFile(
*builds.otatools, target_directories.root, "ota_tools.zip"));
std::vector<std::string> ota_tools_files = CF_EXPECT(
ExtractArchiveContents(otatools_filepath, target_directories.otatools,
keep_downloaded_archives));
const auto [otatools_build_id, otatools_build_target] =
GetBuildIdAndTarget(*builds.otatools);
CF_EXPECT(config.AddFilesToConfig(
FileSource::DEFAULT_BUILD, otatools_build_id, otatools_build_target,
ota_tools_files, target_directories.root));
}
// Wait for ProcessHostPackage to return.
std::vector<std::string> host_package_files =
CF_EXPECT(process_pkg_ret.get());
const auto [host_id, host_target] = GetBuildIdAndTarget(builds.host_package);
FileSource host_filesource = FileSource::DEFAULT_BUILD;
if (is_host_package_build) {
host_filesource = FileSource::HOST_PACKAGE_BUILD;
}
CF_EXPECT(config.AddFilesToConfig(host_filesource, host_id, host_target,
host_package_files,
target_directories.root));
return {};
}
Result<void> Fetch(const FetchFlags& flags,
const std::vector<Target>& targets) {
#ifdef __BIONIC__
// TODO(schuffelen): Find a better way to deal with tzdata
setenv("ANDROID_TZDATA_ROOT", "/", /* overwrite */ 0);
setenv("ANDROID_ROOT", "/", /* overwrite */ 0);
#endif
curl_global_init(CURL_GLOBAL_DEFAULT);
{
BuildApi build_api = CF_EXPECT(GetBuildApi(flags.build_api_flags));
for (const auto& target : targets) {
LOG(INFO) << "Starting fetch to \"" << target.directories.root << "\"";
FetcherConfig config;
const Builds builds =
CF_EXPECT(GetBuilds(build_api, target.build_strings));
const bool is_host_package_build =
target.build_strings.host_package_build.has_value();
CF_EXPECT(FetchTarget(
build_api, builds, target.directories, target.download_flags,
flags.keep_downloaded_archives, is_host_package_build, config));
CF_EXPECT(SaveConfig(config, target.directories.root));
LOG(INFO) << "Completed fetch to \"" << target.directories.root << "\"";
}
}
curl_global_cleanup();
LOG(INFO) << "Completed all fetches";
return {};
}
} // namespace
Result<void> FetchCvdMain(int argc, char** argv) {
android::base::InitLogging(argv, android::base::StderrLogger);
const FetchFlags flags = CF_EXPECT(GetFlagValues(argc, argv));
const std::vector<Target> targets = GetFetchTargets(flags);
CF_EXPECT(EnsureDirectoriesExist(flags.target_directory, targets));
android::base::SetLogger(
LogToStderrAndFiles({flags.target_directory + "/fetch.log"}));
android::base::SetMinimumLogSeverity(flags.verbosity);
auto result = Fetch(flags, targets);
if (!result.ok()) {
LOG(ERROR) << result.error().FormatForEnv();
}
return result;
}
} // namespace cuttlefish