using ::android::base::Basename; using ::android::base::Dirname; using ::android::base::Join; using ::android::base::ParseInt; using ::android::base::Result; using ::android::base::ScopeGuard; using ::android::base::SetProperty; using ::android::base::Split; using ::android::base::StringPrintf; using ::android::base::Timer; using ::android::modules::sdklevel::IsAtLeastU; using ::android::modules::sdklevel::IsAtLeastV; using ::art::tools::CmdlineBuilder;
// Name of cache info file in the ART Apex artifact cache.
constexpr constchar* kCacheInfoFile = "cache-info.xml";
// Maximum execution time for odrefresh from start to end.
constexpr time_t kMaximumExecutionSeconds = 480;
// Maximum execution time for any child process spawned.
constexpr time_t kMaxChildProcessSeconds = 120;
// Moves `files` to the directory `output_directory_path`. // // If any of the files cannot be moved, then all copies of the files are removed from both // the original location and the output location. // // Returns true if all files are moved, false otherwise. bool MoveOrEraseFiles(const std::vector<std::unique_ptr<File>>& files,
std::string_view output_directory_path,
android::base::function_ref<int(constchar*, unsignedint)> restorecon) {
std::vector<std::unique_ptr<File>> output_files; for (auto& file : files) {
std::string file_basename(Basename(file->GetPath()));
std::string output_file_path = ART_FORMAT("{}/{}", output_directory_path, file_basename);
std::string input_file_path = file->GetPath();
if (IsAtLeastV()) { // Simply rename the existing file. Requires at least V as odrefresh does not have // `selinux_android_restorecon` permissions on U and lower. if (!file->Rename(output_file_path)) {
PLOG(ERROR) << "Failed to rename " << QuotePath(input_file_path) << " to "
<< QuotePath(output_file_path);
EraseFiles(files); returnfalse;
}
if (file->FlushCloseOrErase() != 0) {
PLOG(ERROR) << "Failed to flush and close file " << QuotePath(output_file_path);
EraseFiles(files); returnfalse;
}
if (restorecon(output_file_path.c_str(), 0) < 0) {
LOG(ERROR) << "Failed to set security context for file " << QuotePath(output_file_path);
EraseFiles(files); returnfalse;
}
} else { // Create a new file in the output directory, copy the input file's data across, then delete // the input file.
output_files.emplace_back(OS::CreateEmptyFileWriteOnly(output_file_path.c_str())); if (output_files.back() == nullptr) {
PLOG(ERROR) << "Failed to open " << QuotePath(output_file_path);
output_files.pop_back();
EraseFiles(output_files);
EraseFiles(files); returnfalse;
}
if (fchmod(output_files.back()->Fd(), kFileMode) != 0) {
PLOG(ERROR) << "Could not set file mode on " << QuotePath(output_file_path);
EraseFiles(output_files);
EraseFiles(files); returnfalse;
}
size_t file_bytes = file->GetLength(); if (!output_files.back()->Copy(file.get(), /*offset=*/0, file_bytes)) {
PLOG(ERROR) << "Failed to copy " << QuotePath(file->GetPath()) << " to "
<< QuotePath(output_file_path);
EraseFiles(output_files);
EraseFiles(files); returnfalse;
}
if (!file->Erase(/*unlink=*/true)) {
PLOG(ERROR) << "Failed to erase " << QuotePath(file->GetPath());
EraseFiles(output_files);
EraseFiles(files); returnfalse;
}
if (output_files.back()->FlushCloseOrErase() != 0) {
PLOG(ERROR) << "Failed to flush and close file " << QuotePath(output_file_path);
EraseFiles(output_files);
EraseFiles(files); returnfalse;
}
}
} returntrue;
}
// Gets the `ApexInfo` associated with the currently active ART APEX.
std::optional<apex::ApexInfo> GetArtApexInfo(const std::vector<apex::ApexInfo>& info_list) { auto it = std::find_if(info_list.begin(), info_list.end(), [](const apex::ApexInfo& info) { return info.getModuleName() == "com.android.art";
}); return it != info_list.end() ? std::make_optional(*it) : std::nullopt;
}
// Returns cache provenance information based on the current APEX version and filesystem // information.
art_apex::ModuleInfo GenerateModuleInfo(const apex::ApexInfo& apex_info) { // The lastUpdateMillis is an addition to ApexInfoList.xsd to support samegrade installs.
int64_t last_update_millis =
apex_info.hasLastUpdateMillis() ? apex_info.getLastUpdateMillis() : 0; return art_apex::ModuleInfo{apex_info.getModuleName(),
apex_info.getVersionCode(),
apex_info.getVersionName(),
last_update_millis};
}
// Returns cache provenance information for all APEXes.
std::vector<art_apex::ModuleInfo> GenerateModuleInfoList( const std::vector<apex::ApexInfo>& apex_info_list) {
std::vector<art_apex::ModuleInfo> module_info_list;
std::transform(apex_info_list.begin(),
apex_info_list.end(),
std::back_inserter(module_info_list),
GenerateModuleInfo); return module_info_list;
}
// Returns a rewritten path based on environment variables for interesting paths.
std::string RewriteParentDirectoryIfNeeded(const std::string& path) { if (path.starts_with("/system/")) { return GetAndroidRoot() + path.substr(7);
} elseif (path.starts_with("/system_ext/")) { return GetSystemExtRoot() + path.substr(11);
} else { return path;
}
}
// Checks whether a group of artifacts exists. Returns true if all are present, false otherwise. // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`. bool ArtifactsExist(const OdrArtifacts& artifacts, bool check_art_file, /*out*/ std::string* error_msg, /*out*/ std::vector<std::string>* checked_artifacts = nullptr) {
std::vector<constchar*> paths{artifacts.OatPath().c_str(), artifacts.VdexPath().c_str()}; if (check_art_file) {
paths.push_back(artifacts.ImagePath().c_str());
} for (constchar* path : paths) { if (!OS::FileExists(path)) { if (errno == EACCES) {
PLOG(ERROR) << "Failed to stat() " << path;
}
*error_msg = "Missing file: " + QuotePath(path); returnfalse;
}
} // This should be done after checking all artifacts because either all of them are valid or none // of them is valid. if (checked_artifacts != nullptr) { for (constchar* path : paths) {
checked_artifacts->emplace_back(path);
}
} returntrue;
}
if (oat_file.get() == nullptr) { // This shouldn't happen except in tests. It is safe to return true here, // since it isn't a valid oat file. returntrue;
}
// Avoid storing dex2oat cmdline in oat header. We want to be sure that the compiled artifacts // are identical regardless of where the compilation happened. But some of the cmdline flags tends // to be unstable, e.g. those contains FD numbers. To avoid the problem, the whole cmdline is not // added to the oat header.
args.Add("--avoid-storing-invocation");
// Returns the number of profiles that have been added, or error if an error occurred.
Result<int> AddDex2OatProfile( /*inout*/ CmdlineBuilder& args, /*inout*/ std::vector<std::unique_ptr<File>>& output_files, const std::vector<std::string>& profile_paths) { int added_profile_count = 0; for (const std::string& path : profile_paths) {
std::unique_ptr<File> profile_file(OS::OpenFileForReading(path.c_str())); if (profile_file != nullptr) {
args.Add("--profile-file-fd=%d", profile_file->Fd());
output_files.emplace_back(std::move(profile_file));
added_profile_count++;
} elseif (errno != ENOENT) { return ErrnoErrorf("Failed to open profile file '{}'", path);
}
} return added_profile_count;
}
Result<void> AddBootClasspathFds(/*inout*/ CmdlineBuilder& args, /*inout*/ std::vector<std::unique_ptr<File>>& output_files, const std::vector<std::string>& bcp_jars) {
std::vector<std::string> bcp_fds; for (const std::string& jar : bcp_jars) { // Special treatment for Compilation OS. JARs in staged APEX may not be visible to Android, and // may only be visible in the VM where the staged APEX is mounted. On the contrary, JARs in // /system is not available by path in the VM, and can only made available via (remote) FDs. if (jar.starts_with("/apex/")) {
bcp_fds.emplace_back("-1");
} else {
std::string actual_path = RewriteParentDirectoryIfNeeded(jar);
std::unique_ptr<File> jar_file(OS::OpenFileForReading(actual_path.c_str())); if (jar_file == nullptr) { return ErrnoErrorf("Failed to open a BCP jar '{}'", actual_path);
}
bcp_fds.push_back(std::to_string(jar_file->Fd()));
output_files.push_back(std::move(jar_file));
}
}
args.AddRuntime("-Xbootclasspathfds:%s", Join(bcp_fds, ':')); return {};
}
Result<void> AddCacheInfoFd(/*inout*/ CmdlineBuilder& args, /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii, const std::string& cache_info_filename) {
std::unique_ptr<File> cache_info_file(OS::OpenFileForReading(cache_info_filename.c_str())); if (cache_info_file == nullptr) { return ErrnoErrorf("Failed to open a cache info file '{}'", cache_info_filename);
}
WARN_UNUSED bool CheckCompilationSpace() { // Check the available storage space against an arbitrary threshold because dex2oat does not // report when it runs out of storage space and we do not want to completely fill // the users data partition. // // We do not have a good way of pre-computing the required space for a compilation step, but // typically observe no more than 48MiB as the largest total size of AOT artifacts for a single // dex2oat invocation, which includes an image file, an executable file, and a verification data // file. static constexpr uint64_t kMinimumSpaceForCompilation = 48 * 1024 * 1024;
if (bytes_available < kMinimumSpaceForCompilation) {
LOG(WARNING) << "Low space for " << QuotePath(art_apex_data_path) << " (" << bytes_available
<< " bytes)"; returnfalse;
}
returntrue;
}
bool HasVettedDeviceSystemServerProfiles() { // While system_server profiles were bundled on the device prior to U+, they were not used by // default or rigorously tested, so we cannot vouch for their efficacy. staticconstbool kDeviceIsAtLeastU = IsAtLeastU(); return kDeviceIsAtLeastU;
}
} // namespace
CompilationOptions CompilationOptions::CompileAll(const OnDeviceRefresh& odr) {
CompilationOptions options; for (InstructionSet isa : odr.Config().GetBootClasspathIsas()) {
options.boot_images_to_generate_for_isas.emplace_back(
isa, BootImages{.primary_boot_image = true, .boot_image_mainline_extension = true});
}
options.system_server_jars_to_compile = odr.AllSystemServerJars(); return options;
}
int BootImages::Count() const { int count = 0; if (primary_boot_image) {
count++;
} if (boot_image_mainline_extension) {
count++;
} return count;
}
OnDeviceRefresh::OnDeviceRefresh( const OdrConfig& config,
android::base::function_ref<int(constchar*, constchar*)> setfilecon,
android::base::function_ref<int(constchar*, unsignedint)> restorecon, const std::string& cache_info_filename,
std::unique_ptr<ExecUtils> exec_utils,
android::base::function_ref<bool()> check_compilation_space)
: config_(config),
cache_info_filename_(cache_info_filename),
start_time_(time(nullptr)),
exec_utils_(std::move(exec_utils)),
check_compilation_space_(check_compilation_space),
setfilecon_(setfilecon),
restorecon_(restorecon) { // Updatable APEXes should not have DEX files in the DEX2OATBOOTCLASSPATH. At the time of // writing i18n is a non-updatable APEX and so does appear in the DEX2OATBOOTCLASSPATH.
dex2oat_boot_classpath_jars_ = Split(config_.GetDex2oatBootClasspath(), ":");
std::error_code ec; if (std::filesystem::exists(staging_dir, ec)) { if (std::filesystem::remove_all(staging_dir, ec) < 0) { return Errorf( "Could not remove existing staging directory '{}': {}", staging_dir, ec.message());
}
}
if (mkdir(staging_dir.c_str(), S_IRWXU) != 0) { return ErrnoErrorf("Could not create staging directory '{}'", staging_dir);
}
if (setfilecon_(staging_dir.c_str(), "u:object_r:apex_art_staging_data_file:s0") != 0) { return ErrnoErrorf("Could not set label on staging directory '{}'", staging_dir);
}
// We are only interested in active APEXes that contain compilable JARs.
std::unordered_set<std::string_view> relevant_apexes;
relevant_apexes.reserve(info_list->getApexInfo().size()); for (const std::vector<std::string>* jar_list :
{&all_systemserver_jars_, &boot_classpath_jars_}) { for (const std::string& jar : *jar_list) {
std::string_view apex = ApexNameFromLocation(jar); if (!apex.empty()) {
relevant_apexes.insert(apex);
}
}
} // The ART APEX is always relevant no matter it contains any compilable JAR or not, because it // contains the runtime.
relevant_apexes.insert("com.android.art");
Result<art_apex::CacheInfo> OnDeviceRefresh::ReadCacheInfo() const {
std::optional<art_apex::CacheInfo> cache_info = art_apex::read(cache_info_filename_.c_str()); if (!cache_info.has_value()) { if (errno != 0) { return ErrnoErrorf("Failed to load {}", QuotePath(cache_info_filename_));
} else { return Errorf("Failed to parse {}", QuotePath(cache_info_filename_));
}
} return cache_info.value();
}
// This function has a large stack frame, so avoid inlining it because doing so // could push its caller's stack frame over the limit. See b/330851312.
NO_INLINE Result<void> OnDeviceRefresh::WriteCacheInfo() const { if (OS::FileExists(cache_info_filename_.c_str())) { if (unlink(cache_info_filename_.c_str()) != 0) { return ErrnoErrorf("Failed to unlink file {}", QuotePath(cache_info_filename_));
}
}
std::string dir_name = Dirname(cache_info_filename_); if (!EnsureDirectoryExists(dir_name)) { return Errorf("Could not create directory {}", QuotePath(dir_name));
}
std::vector<art_apex::KeyValuePair> system_properties; for (constauto& [key, value] : config_.GetSystemProperties()) { if (!art::ContainsElement(kIgnoredSystemProperties, key)) {
system_properties.emplace_back(key, value);
}
}
std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList(); if (!apex_info_list.has_value()) { return Errorf("Could not update {}: no APEX info", QuotePath(cache_info_filename_));
}
std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list.value()); if (!art_apex_info.has_value()) { return Errorf("Could not update {}: no ART APEX info", QuotePath(cache_info_filename_));
}
std::ofstream out(cache_info_filename_.c_str()); if (out.fail()) { return ErrnoErrorf("Could not create cache info file {}", QuotePath(cache_info_filename_));
}
art_apex::write(out, *info);
out.close(); if (out.fail()) { return ErrnoErrorf("Could not write cache info file {}", QuotePath(cache_info_filename_));
}
return {};
}
staticvoid ReportNextBootAnimationProgress(uint32_t current_compilation,
uint32_t number_of_compilations) { // We arbitrarily show progress until 90%, expecting that our compilations take a large chunk of // boot time.
uint32_t value =
number_of_compilations != 0 ? (90 * current_compilation) / number_of_compilations : 90;
SetProperty("service.bootanim.progress", std::to_string(value));
}
std::vector<std::string> OnDeviceRefresh::GetArtBcpJars() const {
std::string art_root = GetArtRoot() + "/";
std::vector<std::string> art_bcp_jars; for (const std::string& jar : dex2oat_boot_classpath_jars_) { if (jar.starts_with(art_root)) {
art_bcp_jars.push_back(jar);
}
}
CHECK(!art_bcp_jars.empty()); return art_bcp_jars;
}
std::vector<std::string> OnDeviceRefresh::GetFrameworkBcpJars() const {
std::string art_root = GetArtRoot() + "/";
std::vector<std::string> framework_bcp_jars; for (const std::string& jar : dex2oat_boot_classpath_jars_) { if (!jar.starts_with(art_root)) {
framework_bcp_jars.push_back(jar);
}
}
CHECK(!framework_bcp_jars.empty()); return framework_bcp_jars;
}
std::vector<std::string> OnDeviceRefresh::GetMainlineBcpJars() const { // Elements in `dex2oat_boot_classpath_jars_` should be at the beginning of // `boot_classpath_jars_`, followed by mainline BCP jars.
CHECK_LT(dex2oat_boot_classpath_jars_.size(), boot_classpath_jars_.size());
CHECK(std::equal(dex2oat_boot_classpath_jars_.begin(),
dex2oat_boot_classpath_jars_.end(),
boot_classpath_jars_.begin(),
boot_classpath_jars_.begin() + dex2oat_boot_classpath_jars_.size())); return {boot_classpath_jars_.begin() + dex2oat_boot_classpath_jars_.size(),
boot_classpath_jars_.end()};
}
std::vector<std::string> OnDeviceRefresh::GetBestBootImages(InstructionSet isa, bool include_mainline_extension) const {
std::vector<std::string> locations;
std::string unused_error_msg; bool primary_on_data = false; if (PrimaryBootImageExist( /*on_system=*/false, /*minimal=*/false, isa, &unused_error_msg)) {
primary_on_data = true;
locations.push_back(GetPrimaryBootImage(/*on_system=*/false, /*minimal=*/false));
} else {
locations.push_back(GetPrimaryBootImage(/*on_system=*/true, /*minimal=*/false)); if (!IsAtLeastU()) { // Prior to U, there was a framework extension.
locations.push_back(GetSystemBootImageFrameworkExtension());
}
} if (include_mainline_extension) { if (BootImageMainlineExtensionExist(/*on_system=*/false, isa, &unused_error_msg)) {
locations.push_back(GetBootImageMainlineExtension(/*on_system=*/false));
} else { // If the primary boot image is on /data, it means we have regenerated all boot images, so the // mainline extension must be on /data too.
CHECK(!primary_on_data)
<< "Mainline extension not found while primary boot image is on /data";
locations.push_back(GetBootImageMainlineExtension(/*on_system=*/true));
}
} return locations;
}
WARN_UNUSED bool OnDeviceRefresh::RemoveArtifactsDirectory() const { if (config_.GetDryRun()) {
LOG(INFO) << "Directory " << QuotePath(config_.GetArtifactDirectory())
<< " and contents would be removed (dry-run)."; returntrue;
} return RemoveDirectory(config_.GetArtifactDirectory());
}
WARN_UNUSED bool OnDeviceRefresh::PrimaryBootImageExist( bool on_system, bool minimal,
InstructionSet isa, /*out*/ std::string* error_msg, /*out*/ std::vector<std::string>* checked_artifacts) const {
std::string path = GetPrimaryBootImagePath(on_system, minimal, isa);
OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path); if (!ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) { returnfalse;
} if (!CheckOatHeader(artifacts.OatPath(), error_msg)) { returnfalse;
} // Prior to U, there was a split between the primary boot image and the extension on /system, so // they need to be checked separately. This does not apply to the boot image on /data. if (on_system && !IsAtLeastU()) {
std::string extension_path = GetSystemBootImageFrameworkExtensionPath(isa);
OdrArtifacts extension_artifacts = OdrArtifacts::ForBootImage(extension_path); if (!ArtifactsExist(
extension_artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) { returnfalse;
}
} returntrue;
}
bool OnDeviceRefresh::SystemServerArtifactsExist( bool on_system, /*out*/ std::string* error_msg, /*out*/ std::set<std::string>* jars_missing_artifacts, /*out*/ std::vector<std::string>* checked_artifacts) const { for (const std::string& jar_path : all_systemserver_jars_) { const std::string image_location = GetSystemServerImagePath(on_system, jar_path); const OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location); // .art files are optional and are not generated for all jars by the build system. constbool check_art_file = !on_system;
std::string error_msg_tmp; if (!ArtifactsExist(artifacts, check_art_file, &error_msg_tmp, checked_artifacts)) {
jars_missing_artifacts->insert(jar_path);
*error_msg = error_msg->empty() ? error_msg_tmp : *error_msg + "\n" + error_msg_tmp; continue;
} if (!CheckOatHeader(artifacts.OatPath(), &error_msg_tmp)) {
jars_missing_artifacts->insert(jar_path);
*error_msg = error_msg->empty() ? error_msg_tmp : *error_msg + "\n" + error_msg_tmp;
}
} return jars_missing_artifacts->empty();
}
WARN_UNUSED bool OnDeviceRefresh::CheckSystemPropertiesAreDefault() const { // We don't have to check properties that match `kCheckedSystemPropertyPrefixes` here because none // of them is persistent. This only applies when `cache-info.xml` does not exist. When // `cache-info.xml` exists, we call `CheckSystemPropertiesHaveNotChanged` instead.
DCHECK(std::none_of(std::begin(kCheckedSystemPropertyPrefixes),
std::end(kCheckedSystemPropertyPrefixes),
[](std::string_view prefix) { return prefix.starts_with("persist."); }));
for (const SystemPropertyConfig& system_property_config : *kSystemProperties.get()) { // Note that the `kSystemPropertySystemServerCompilerFilterOverride` property has an empty // default value, so we use the `GetOrNull` method and check against nullopt
std::optional<std::string> property = system_properties.GetOrNull(system_property_config.name);
DCHECK(property.has_value()) << "Property " << system_property_config.name
<< " does not exist in system properties map!";
if (*property != system_property_config.default_value) {
LOG(INFO) << "System property " << system_property_config.name << " has a non-default value ("
<< *property << ")."; returnfalse;
}
}
const art_apex::KeyValuePairList* list = cache_info.getFirstSystemProperties(); if (list == nullptr) { // This should never happen. We have already checked the ART module version, and the cache // info is generated by the latest version of the ART module if it exists.
LOG(ERROR) << "Missing system properties from cache-info."; returnfalse;
}
if (!CheckBuildUserfaultFdGc()) { return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
}
std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list); if (!art_apex_info.has_value()) { // This should never happen, further up-to-date checks are not possible if it does.
LOG(ERROR) << "Could not get ART APEX info."; return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kUnknown);
}
if (!art_apex_info->getIsFactory()) {
LOG(INFO) << "Updated ART APEX mounted"; return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
}
if (cached_info.getVersionName() != current_info.getVersionName()) {
LOG(INFO) << ART_FORMAT("APEX ({}) version name mismatch (before: {}, now: {})",
current_info.getModuleName(),
cached_info.getVersionName(),
current_info.getVersionName()); returnfalse;
}
// Check lastUpdateMillis for samegrade installs. If `cached_info` is missing the lastUpdateMillis // field then it is not current with the schema used by this binary so treat it as a samegrade // update. Otherwise check whether the lastUpdateMillis changed. const int64_t cached_last_update_millis =
cached_info.hasLastUpdateMillis() ? cached_info.getLastUpdateMillis() : -1; if (cached_last_update_millis != current_info.getLastUpdateMillis()) {
LOG(INFO) << ART_FORMAT("APEX ({}) last update time mismatch (before: {}, now: {})",
current_info.getModuleName(),
cached_info.getLastUpdateMillis(),
current_info.getLastUpdateMillis()); returnfalse;
}
returntrue;
}
WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForData( const std::vector<com::android::apex::ApexInfo>& apex_info_list) const {
Result<art_apex::CacheInfo> cache_info = ReadCacheInfo(); if (!cache_info.ok()) { if (cache_info.error().code() == ENOENT) { // If the cache info file does not exist, it usually means it's the first boot, or the // dalvik-cache directory is cleared by odsign due to corrupted files. Set the trigger to be // `kApexVersionMismatch` to force generate the cache info file and compile if necessary.
LOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
} else { // This should not happen unless odrefresh is updated to a new version that is not compatible // with an old cache-info file. Further up-to-date checks are not possible if it does.
LOG(ERROR) << cache_info.error().message();
} return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
}
if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) { // We don't have a trigger kind for system property changes. For now, we reuse // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last // compilation attempt. return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
}
// Check whether the current cache ART module info differs from the current ART module info. const art_apex::ModuleInfo* cached_art_info = cache_info->getFirstArtModuleInfo(); if (cached_art_info == nullptr) {
LOG(ERROR) << "Missing ART APEX info from cache-info."; return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
}
std::optional<apex::ApexInfo> current_art_info = GetArtApexInfo(apex_info_list); if (!current_art_info.has_value()) { // This should never happen, further up-to-date checks are not possible if it does.
LOG(ERROR) << "Could not get ART APEX info."; return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kUnknown);
}
if (!CheckModuleInfo(*cached_art_info, *current_art_info)) { return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
}
// Check boot class components. // // This checks the size and checksums of odrefresh compilable files on the DEX2OATBOOTCLASSPATH // (the Odrefresh constructor determines which files are compilable). If the number of files // there changes, or their size or checksums change then compilation will be triggered. // // The boot class components may change unexpectedly, for example an OTA could update // framework.jar. const std::vector<art_apex::Component> current_dex2oat_bcp_components =
GenerateDex2oatBootClasspathComponents();
Result<void> result = CheckComponents(current_dex2oat_bcp_components,
cached_dex2oat_bcp_components->getComponent()); if (!result.ok()) {
LOG(INFO) << "Dex2OatClasspath components mismatch: " << result.error(); return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kDexFilesChanged);
}
// Check whether the current cached module info differs from the current module info. const art_apex::ModuleInfoList* cached_module_info_list = cache_info->getFirstModuleInfoList(); if (cached_module_info_list == nullptr) {
LOG(ERROR) << "Missing APEX info list from cache-info."; return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
OdrMetrics::Trigger::kApexVersionMismatch);
}
// Note that apex_info_list may omit APEXes that are included in cached_module_info - e.g. if an // apex used to be compilable, but now isn't. That won't be detected by this loop, but will be // detected below in CheckComponents. for (const apex::ApexInfo& current_apex_info : apex_info_list) { auto& apex_name = current_apex_info.getModuleName();
auto it = cached_module_info_map.find(apex_name); if (it == cached_module_info_map.end()) {
LOG(INFO) << "Missing APEX info from cache-info (" << apex_name << ")."; return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
OdrMetrics::Trigger::kApexVersionMismatch);
}
result = CheckComponents(current_bcp_components, cached_bcp_components->getComponent()); if (!result.ok()) {
LOG(INFO) << "BootClasspath components mismatch: " << result.error(); // Boot classpath components can be dependencies of system_server components, so system_server // components need to be recompiled if boot classpath components are changed. return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
OdrMetrics::Trigger::kDexFilesChanged);
}
// Check system server components. // // This checks the size and checksums of odrefresh compilable files on the // SYSTEMSERVERCLASSPATH (the Odrefresh constructor determines which files are compilable). If // the number of files there changes, or their size or checksums change then compilation will be // triggered. // // The system_server components may change unexpectedly, for example an OTA could update // services.jar. const std::vector<art_apex::SystemServerComponent> current_system_server_components =
GenerateSystemServerComponents();
BootImages boot_images_on_system{.primary_boot_image = false,
.boot_image_mainline_extension = false}; if (system_result.IsPrimaryBootImageOk()) { // We can use the artifacts on /system. Check if they exist.
std::string error_msg; if (PrimaryBootImageExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) {
boot_images_on_system.primary_boot_image = true;
} else {
LOG(INFO) << "Incomplete primary boot image or framework extension on /system: " << error_msg;
}
}
if (boot_images_on_system.primary_boot_image && system_result.IsBootImageMainlineExtensionOk()) {
std::string error_msg; if (BootImageMainlineExtensionExist(/*on_system=*/true, isa, &error_msg)) {
boot_images_on_system.boot_image_mainline_extension = true;
} else {
LOG(INFO) << "Incomplete boot image mainline extension on /system: " << error_msg;
}
}
if (boot_images_on_system.Count() == BootImages::kMaxCount) {
LOG(INFO) << ART_FORMAT("Boot images on /system OK ({})", isa_str); // Nothing to compile. return BootImages{.primary_boot_image = false, .boot_image_mainline_extension = false};
}
if (data_result.IsPrimaryBootImageOk()) {
std::string error_msg; if (PrimaryBootImageExist( /*on_system=*/false, /*minimal=*/false, isa, &error_msg, checked_artifacts)) {
boot_images_on_data.primary_boot_image = true;
} else {
LOG(INFO) << "Incomplete primary boot image on /data: " << error_msg;
metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); // Add the minimal boot image to `checked_artifacts` if exists. This is to prevent the minimal // boot image from being deleted. It does not affect the return value because we should still // attempt to generate a full boot image even if the minimal one exists. if (PrimaryBootImageExist( /*on_system=*/false, /*minimal=*/true, isa, &error_msg, checked_artifacts)) {
LOG(INFO) << ART_FORMAT("Found minimal primary boot image ({})", isa_str);
}
}
} else {
metrics.SetTrigger(data_result.GetTrigger());
}
if (boot_images_on_system.primary_boot_image || boot_images_on_data.primary_boot_image) { if (data_result.IsBootImageMainlineExtensionOk()) {
std::string error_msg; if (BootImageMainlineExtensionExist( /*on_system=*/false, isa, &error_msg, checked_artifacts)) {
boot_images_on_data.boot_image_mainline_extension = true;
} else {
LOG(INFO) << "Incomplete boot image mainline extension on /data: " << error_msg;
metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
}
} else {
metrics.SetTrigger(data_result.GetTrigger());
}
}
if (boot_images_to_generate.Count() == 0) {
LOG(INFO) << ART_FORMAT("Boot images on /data OK ({})", isa_str);
}
return boot_images_to_generate;
}
std::set<std::string> OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate(
OdrMetrics& metrics, const PreconditionCheckResult& system_result, const PreconditionCheckResult& data_result, /*out*/ std::vector<std::string>* checked_artifacts) const {
std::set<std::string> jars_to_compile;
std::set<std::string> jars_missing_artifacts_on_system; if (system_result.IsSystemServerOk()) { // We can use the artifacts on /system. Check if they exist.
std::string error_msg; if (SystemServerArtifactsExist( /*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) {
LOG(INFO) << "system_server artifacts on /system OK"; return {};
}
LOG(INFO) << "Incomplete system server artifacts on /system: " << error_msg;
LOG(INFO) << "Checking system server artifacts /data";
} else {
jars_missing_artifacts_on_system = AllSystemServerJars();
}
// When anything unexpected happens, remove all artifacts. auto remove_artifact_dir = android::base::make_scope_guard([&]() { if (!RemoveDirectory(artifact_dir)) {
LOG(ERROR) << "Failed to remove the artifact directory";
}
});
std::vector<std::filesystem::directory_entry> entries;
std::error_code ec; for (constauto& entry : std::filesystem::recursive_directory_iterator(artifact_dir, ec)) { // Save the entries and use them later because modifications during the iteration will result in // undefined behavior;
entries.push_back(entry);
} if (ec && ec.value() != ENOENT) {
metrics.SetStatus(ec.value() == EPERM ? OdrMetrics::Status::kDalvikCachePermissionDenied
: OdrMetrics::Status::kIoError); return Errorf("Failed to iterate over entries in the artifact directory: {}", ec.message());
}
for (const std::filesystem::directory_entry& entry : entries) {
std::string path = entry.path().string(); if (entry.is_regular_file()) { if (!ContainsElement(artifact_set, path)) {
LOG(INFO) << "Removing " << path; if (unlink(path.c_str()) != 0) {
metrics.SetStatus(OdrMetrics::Status::kIoError); return ErrnoErrorf("Failed to remove file {}", QuotePath(path));
}
}
} elseif (!entry.is_directory()) { // Neither a regular file nor a directory. Unexpected file type.
LOG(INFO) << "Removing " << path; if (unlink(path.c_str()) != 0) {
metrics.SetStatus(OdrMetrics::Status::kIoError); return ErrnoErrorf("Failed to remove file {}", QuotePath(path));
}
}
}
std::vector<std::filesystem::directory_entry> entries;
std::error_code ec; for (constauto& entry : std::filesystem::recursive_directory_iterator(artifact_dir, ec)) { // Save the entries and use them later because modifications during the iteration will result in // undefined behavior;
entries.push_back(entry);
} if (ec) { return Errorf("Failed to iterate over entries in the artifact directory: {}", ec.message());
}
for (const std::filesystem::directory_entry& entry : entries) {
std::string path = entry.path().string(); if (entry.is_regular_file()) { // Unexpected files are already removed by `CleanupArtifactDirectory`. We can safely assume // that all the remaining files are good.
LOG(INFO) << "Refreshing " << path;
std::string content; if (!android::base::ReadFileToString(path, &content)) { return Errorf("Failed to read file {}", QuotePath(path));
} if (unlink(path.c_str()) != 0) { return ErrnoErrorf("Failed to remove file {}", QuotePath(path));
} if (!android::base::WriteStringToFile(content, path)) { return Errorf("Failed to write file {}", QuotePath(path));
} if (chmod(path.c_str(), kFileMode) != 0) { return ErrnoErrorf("Failed to chmod file {}", QuotePath(path));
}
}
}
// Clean-up helper used to simplify clean-ups and handling failures there. auto cleanup_and_compile_all = [&, this]() {
*compilation_options = CompilationOptions::CompileAll(*this); if (!RemoveArtifactsDirectory()) {
metrics.SetStatus(OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed;
} return ExitCode::kCompilationRequired;
};
std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList(); if (!apex_info_list.has_value()) { // This should never happen, further up-to-date checks are not possible if it does.
LOG(ERROR) << "Could not get APEX info.";
metrics.SetTrigger(OdrMetrics::Trigger::kUnknown); return cleanup_and_compile_all();
}
std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list.value()); if (!art_apex_info.has_value()) { // This should never happen, further up-to-date checks are not possible if it does.
LOG(ERROR) << "Could not get ART APEX info.";
metrics.SetTrigger(OdrMetrics::Trigger::kUnknown); return cleanup_and_compile_all();
}
// Record ART APEX version for metrics reporting.
metrics.SetArtApexVersion(art_apex_info->getVersionCode());
// Log the version so there's a starting point for any issues reported (b/197489543).
LOG(INFO) << "ART APEX version " << art_apex_info->getVersionCode();
// Record ART APEX last update milliseconds (used in compilation log).
metrics.SetArtApexLastUpdateMillis(art_apex_info->getLastUpdateMillis());
if (!compilation_required && !data_result.IsAllOk()) { // Return kCompilationRequired to generate the cache info even if there's nothing to compile.
compilation_required = true;
metrics.SetTrigger(data_result.GetTrigger());
}
// Always keep the cache info.
checked_artifacts.push_back(cache_info_filename_);
Result<void> result = CleanupArtifactDirectory(metrics, checked_artifacts); if (!result.ok()) {
LOG(ERROR) << result.error(); return ExitCode::kCleanupFailed;
}
AddDex2OatCommonOptions(args, config_.GetSystemProperties());
AddDex2OatDebugInfo(args);
AddDex2OatInstructionSet(args, isa, config_.GetSystemProperties());
Result<void> result = AddDex2OatConcurrencyArguments(
args, config_.GetCompilationOsMode(), config_.GetSystemProperties()); if (!result.ok()) { return CompilationResult::Error(OdrMetrics::Status::kUnknown, result.error().message());
}
// dex2oat reads some system properties from cache-info.xml generated by odrefresh.
result = AddCacheInfoFd(args, readonly_files_raii, cache_info_filename_); if (!result.ok()) { return CompilationResult::Error(OdrMetrics::Status::kUnknown, result.error().message());
}
for (const std::string& dex_file : dex_files) {
std::string actual_path = RewriteParentDirectoryIfNeeded(dex_file);
args.Add("--dex-file=%s", dex_file);
std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str())); if (file == nullptr) { return CompilationResult::Error(
OdrMetrics::Status::kIoError,
ART_FORMAT("Failed to open dex file '{}': {}", actual_path, strerror(errno)));
}
args.Add("--dex-fd=%d", file->Fd());
readonly_files_raii.push_back(std::move(file));
}
args.AddRuntime("-Xbootclasspath:%s", Join(boot_classpath, ":"));
result = AddBootClasspathFds(args, readonly_files_raii, boot_classpath); if (!result.ok()) { return CompilationResult::Error(OdrMetrics::Status::kIoError, result.error().message());
}
if (!input_boot_images.empty()) {
args.Add("--boot-image=%s", Join(input_boot_images, ':'));
result = AddCompiledBootClasspathFdsIfAny(
args, readonly_files_raii, boot_classpath, isa, input_boot_images); if (!result.ok()) { return CompilationResult::Error(OdrMetrics::Status::kIoError, result.error().message());
}
}
// We don't care about file state on failure. auto cleanup = ScopeGuard([&] { for (const std::unique_ptr<File>& file : output_files) {
file->MarkUnchecked();
}
});
if (!mainline_boot_image_profiles.empty()) {
Result<int> num_added_profiles =
AddDex2OatProfile(args, readonly_files_raii, mainline_boot_image_profiles);
if (!num_added_profiles.ok()) { return CompilationResult::Error(OdrMetrics::Status::kIoError,
ART_FORMAT("Failed to add BCP mainline profiles: {}",
num_added_profiles.error().message()));
} if (static_cast<size_t>(*num_added_profiles) != mainline_boot_image_profiles.size()) { return CompilationResult::Error(OdrMetrics::Status::kIoError, "Cannot add existing BCP mainline profiles");
}
} else { // Fall back to verify mode if there is no profile.
compiler_filter = "verify";
}
args.Add("--compiler-filter=%s", compiler_filter); // For boot image extensions, dex2oat takes the oat location of the primary boot image and // expends it with the name of the first input dex file.
args.Add("--oat-location=%s",
OdrArtifacts::ForBootImage(
GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/false, isa))
.OatPath());
}
CompilationResult result = CompilationResult::Ok();
if (config_.GetMinimal()) {
result.Merge(
CompilationResult::Error(OdrMetrics::Status::kUnknown, "Minimal boot image requested"));
}
if (!check_compilation_space_()) {
result.Merge(CompilationResult::Error(OdrMetrics::Status::kNoSpace, "Insufficient space"));
}
if (result.IsOk() && boot_images.primary_boot_image) {
CompilationResult primary_result = RunDex2oatForBootClasspath(
staging_dir, "primary",
isa,
dex2oat_boot_classpath_jars_,
dex2oat_boot_classpath_jars_, /*input_boot_images=*/{},
GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/false, isa));
result.Merge(primary_result);
if (primary_result.IsOk()) {
on_dex2oat_success();
// Remove the minimal boot image only if the full boot image is successfully generated.
std::string path = GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/true, isa);
OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
unlink(artifacts.ImagePath().c_str());
unlink(artifacts.OatPath().c_str());
unlink(artifacts.VdexPath().c_str());
}
}
if (!result.IsOk() && boot_images.primary_boot_image) {
LOG(ERROR) << "Compilation of primary BCP failed: " << result.error_msg;
// Fall back to generating a minimal boot image. // The compilation of the full boot image will be retried on later reboots with a backoff // time, and the minimal boot image will be removed once the compilation of the full boot // image succeeds.
std::string ignored_error_msg; if (PrimaryBootImageExist( /*on_system=*/false, /*minimal=*/true, isa, &ignored_error_msg)) {
LOG(INFO) << "Minimal boot image already up-to-date"; return result;
}
std::vector<std::string> art_bcp_jars = GetArtBcpJars();
CompilationResult minimal_result = RunDex2oatForBootClasspath(
staging_dir, "minimal",
isa,
art_bcp_jars,
art_bcp_jars, /*input_boot_images=*/{},
GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/true, isa));
result.Merge(minimal_result);
if (!minimal_result.IsOk()) {
LOG(ERROR) << "Compilation of minimal BCP failed: " << result.error_msg;
}
return result;
}
if (result.IsOk() && boot_images.boot_image_mainline_extension) {
CompilationResult mainline_result =
RunDex2oatForBootClasspath(staging_dir, "mainline",
isa,
GetMainlineBcpJars(),
boot_classpath_jars_,
GetBestBootImages(isa, /*include_mainline_extension=*/false),
GetBootImageMainlineExtensionPath(/*on_system=*/false, isa));
result.Merge(mainline_result);
if (mainline_result.IsOk()) {
on_dex2oat_success();
}
}
if (!result.IsOk() && boot_images.boot_image_mainline_extension) {
LOG(ERROR) << "Compilation of mainline BCP failed: " << result.error_msg;
}
// If partial compilation is disabled, we should compile everything regardless of what's in // `compilation_options`. if (!config_.GetPartialCompilation()) {
compilation_options = CompilationOptions::CompileAll(*this); if (!RemoveArtifactsDirectory()) {
metrics.SetStatus(OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed;
}
}
if (!EnsureDirectoryExists(config_.GetArtifactDirectory())) {
LOG(ERROR) << "Failed to prepare artifact directory";
metrics.SetStatus(errno == EPERM ? OdrMetrics::Status::kDalvikCachePermissionDenied
: OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed;
}
if (config_.GetRefresh()) {
Result<void> result = RefreshExistingArtifacts(); if (!result.ok()) {
LOG(ERROR) << "Failed to refresh existing artifacts: " << result.error();
metrics.SetStatus(OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed;
}
}
// Emit cache info before compiling. This can be used to throttle compilation attempts later.
Result<void> result = WriteCacheInfo(); if (!result.ok()) {
LOG(ERROR) << result.error();
metrics.SetStatus(OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed;
}
if (config_.GetCompilationOsMode()) { // We don't need to stage files in CompOS. If the compilation fails (partially or entirely), // CompOS will not sign any artifacts, and odsign will discard CompOS outputs entirely.
staging_dir = "";
} else { // Create staging area and assign label for generating compilation artifacts.
Result<std::string> res = CreateStagingDirectory(); if (!res.ok()) {
LOG(ERROR) << res.error().message();
metrics.SetStatus(OdrMetrics::Status::kStagingFailed); return ExitCode::kCleanupFailed;
}
staging_dir = res.value();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.