/* * Copyright (C) 2020 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 "odrefresh.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "android-base/chrono_utils.h" #include "android-base/file.h" #include "android-base/function_ref.h" #include "android-base/logging.h" #include "android-base/macros.h" #include "android-base/parseint.h" #include "android-base/properties.h" #include "android-base/result.h" #include "android-base/scopeguard.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" #include "android-modules-utils/sdk_level.h" #include "arch/instruction_set.h" #include "base/file_utils.h" #include "base/logging.h" #include "base/macros.h" #include "base/os.h" #include "base/stl_util.h" #include "base/unix_file/fd_file.h" #include "com_android_apex.h" #include "com_android_art.h" #include "dex/art_dex_file_loader.h" #include "exec_utils.h" #include "gc/collector/mark_compact.h" #include "odr_artifacts.h" #include "odr_common.h" #include "odr_config.h" #include "odr_fs_utils.h" #include "odr_metrics.h" #include "odrefresh/odrefresh.h" #include "selinux/android.h" #include "selinux/selinux.h" #include "tools/cmdline_builder.h" namespace art { namespace odrefresh { namespace { namespace apex = com::android::apex; namespace art_apex = com::android::art; 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 const char* 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; constexpr mode_t kFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; constexpr const char* kFirstBootImageBasename = "boot.art"; constexpr const char* kMinimalBootImageBasename = "boot_minimal.art"; // The default compiler filter for primary boot image. constexpr const char* kPrimaryCompilerFilter = "speed-profile"; // The compiler filter for boot image mainline extension. We don't have profiles for mainline BCP // jars, so we always use "verify". constexpr const char* kMainlineCompilerFilter = "verify"; void EraseFiles(const std::vector>& files) { for (auto& file : files) { file->Erase(/*unlink=*/true); } } // 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>& files, std::string_view output_directory_path) { std::vector> 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); return false; } if (file->FlushCloseOrErase() != 0) { PLOG(ERROR) << "Failed to flush and close file " << QuotePath(output_file_path); EraseFiles(files); return false; } if (selinux_android_restorecon(output_file_path.c_str(), 0) < 0) { LOG(ERROR) << "Failed to set security context for file " << QuotePath(output_file_path); EraseFiles(files); return false; } } 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); return false; } 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); return false; } 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); return false; } if (!file->Erase(/*unlink=*/true)) { PLOG(ERROR) << "Failed to erase " << QuotePath(file->GetPath()); EraseFiles(output_files); EraseFiles(files); return false; } if (output_files.back()->FlushCloseOrErase() != 0) { PLOG(ERROR) << "Failed to flush and close file " << QuotePath(output_file_path); EraseFiles(output_files); EraseFiles(files); return false; } } } return true; } // Gets the `ApexInfo` associated with the currently active ART APEX. std::optional GetArtApexInfo(const std::vector& 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 GenerateModuleInfoList( const std::vector& apex_info_list) { std::vector 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); } else if (path.starts_with("/system_ext/")) { return GetSystemExtRoot() + path.substr(11); } else { return path; } } template Result CheckComponents( const std::vector& expected_components, const std::vector& actual_components, const std::function(const T& expected, const T& actual)>& custom_checker = [](const T&, const T&) -> Result { return {}; }) { if (expected_components.size() != actual_components.size()) { return Errorf( "Component count differs ({} != {})", expected_components.size(), actual_components.size()); } for (size_t i = 0; i < expected_components.size(); ++i) { const T& expected = expected_components[i]; const T& actual = actual_components[i]; if (expected.getFile() != actual.getFile()) { return Errorf( "Component {} file differs ('{}' != '{}')", i, expected.getFile(), actual.getFile()); } if (expected.getSize() != actual.getSize()) { return Errorf( "Component {} size differs ({} != {})", i, expected.getSize(), actual.getSize()); } if (expected.getChecksums() != actual.getChecksums()) { return Errorf("Component {} checksums differ ('{}' != '{}')", i, expected.getChecksums(), actual.getChecksums()); } Result result = custom_checker(expected, actual); if (!result.ok()) { return Errorf("Component {} {}", i, result.error().message()); } } return {}; } Result CheckSystemServerComponents( const std::vector& expected_components, const std::vector& actual_components) { return CheckComponents( expected_components, actual_components, [](const art_apex::SystemServerComponent& expected, const art_apex::SystemServerComponent& actual) -> Result { if (expected.getIsInClasspath() != actual.getIsInClasspath()) { return Errorf("isInClasspath differs ({} != {})", expected.getIsInClasspath(), actual.getIsInClasspath()); } return {}; }); } template std::vector GenerateComponents( const std::vector& jars, const std::function& custom_generator) { std::vector components; for (const std::string& path : jars) { std::string actual_path = RewriteParentDirectoryIfNeeded(path); struct stat sb; if (stat(actual_path.c_str(), &sb) == -1) { PLOG(ERROR) << "Failed to stat component: " << QuotePath(actual_path); return {}; } std::optional checksum; std::string error_msg; ArtDexFileLoader dex_loader(actual_path); if (!dex_loader.GetMultiDexChecksum(&checksum, &error_msg)) { LOG(ERROR) << "Failed to get multi-dex checksum: " << error_msg; return {}; } const std::string checksum_str = checksum.has_value() ? StringPrintf("%08x", checksum.value()) : std::string(); Result component = custom_generator(path, static_cast(sb.st_size), checksum_str); if (!component.ok()) { LOG(ERROR) << "Failed to generate component: " << component.error(); return {}; } components.push_back(*std::move(component)); } return components; } std::vector GenerateComponents(const std::vector& jars) { return GenerateComponents( jars, [](const std::string& path, uint64_t size, const std::string& checksum) { return art_apex::Component{path, size, checksum}; }); } // 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* checked_artifacts = nullptr) { std::vector paths{artifacts.OatPath().c_str(), artifacts.VdexPath().c_str()}; if (check_art_file) { paths.push_back(artifacts.ImagePath().c_str()); } for (const char* path : paths) { if (!OS::FileExists(path)) { if (errno == EACCES) { PLOG(ERROR) << "Failed to stat() " << path; } *error_msg = "Missing file: " + QuotePath(path); return false; } } // 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 (const char* path : paths) { checked_artifacts->emplace_back(path); } } return true; } void AddDex2OatCommonOptions(/*inout*/ CmdlineBuilder& args) { args.Add("--android-root=out/empty"); args.Add("--abort-on-hard-verifier-error"); args.Add("--no-abort-on-soft-verifier-error"); args.Add("--compilation-reason=boot"); args.Add("--image-format=lz4"); args.Add("--force-determinism"); args.Add("--resolve-startup-const-strings=true"); // 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"); } bool IsCpuSetSpecValid(const std::string& cpu_set) { for (const std::string& str : Split(cpu_set, ",")) { int id; if (!ParseInt(str, &id, 0)) { return false; } } return true; } Result AddDex2OatConcurrencyArguments(/*inout*/ CmdlineBuilder& args, bool is_compilation_os, const OdrSystemProperties& system_properties) { std::string threads; if (is_compilation_os) { threads = system_properties.GetOrEmpty("dalvik.vm.background-dex2oat-threads", "dalvik.vm.dex2oat-threads"); } else { threads = system_properties.GetOrEmpty("dalvik.vm.boot-dex2oat-threads"); } args.AddIfNonEmpty("-j%s", threads); std::string cpu_set; if (is_compilation_os) { cpu_set = system_properties.GetOrEmpty("dalvik.vm.background-dex2oat-cpu-set", "dalvik.vm.dex2oat-cpu-set"); } else { cpu_set = system_properties.GetOrEmpty("dalvik.vm.boot-dex2oat-cpu-set"); } if (!cpu_set.empty()) { if (!IsCpuSetSpecValid(cpu_set)) { return Errorf("Invalid CPU set spec '{}'", cpu_set); } args.Add("--cpu-set=%s", cpu_set); } return {}; } void AddDex2OatDebugInfo(/*inout*/ CmdlineBuilder& args) { args.Add("--generate-mini-debug-info"); args.Add("--strip"); } void AddDex2OatInstructionSet(/*inout*/ CmdlineBuilder& args, InstructionSet isa, const OdrSystemProperties& system_properties) { const char* isa_str = GetInstructionSetString(isa); args.Add("--instruction-set=%s", isa_str); std::string features_prop = ART_FORMAT("dalvik.vm.isa.{}.features", isa_str); args.AddIfNonEmpty("--instruction-set-features=%s", system_properties.GetOrEmpty(features_prop)); std::string variant_prop = ART_FORMAT("dalvik.vm.isa.{}.variant", isa_str); args.AddIfNonEmpty("--instruction-set-variant=%s", system_properties.GetOrEmpty(variant_prop)); } // Returns true if any profile has been added, or false if no profile exists, or error if any error // occurred. Result AddDex2OatProfile( /*inout*/ CmdlineBuilder& args, /*inout*/ std::vector>& output_files, const std::vector& profile_paths) { bool has_any_profile = false; for (const std::string& path : profile_paths) { std::unique_ptr 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)); has_any_profile = true; } else if (errno != ENOENT) { return ErrnoErrorf("Failed to open profile file '{}'", path); } } return has_any_profile; } Result AddBootClasspathFds(/*inout*/ CmdlineBuilder& args, /*inout*/ std::vector>& output_files, const std::vector& bcp_jars) { std::vector 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 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 AddCacheInfoFd(/*inout*/ CmdlineBuilder& args, /*inout*/ std::vector>& readonly_files_raii, const std::string& cache_info_filename) { std::unique_ptr 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); } args.Add("--cache-info-fd=%d", cache_info_file->Fd()); readonly_files_raii.push_back(std::move(cache_info_file)); return {}; } std::string GetBootImageComponentBasename(const std::string& jar_path, bool is_first_jar) { if (is_first_jar) { return kFirstBootImageBasename; } std::string jar_name = Basename(jar_path); return "boot-" + ReplaceFileExtension(jar_name, "art"); } Result AddCompiledBootClasspathFdsIfAny( /*inout*/ CmdlineBuilder& args, /*inout*/ std::vector>& output_files, const std::vector& bcp_jars, InstructionSet isa, const std::vector& boot_image_locations) { std::vector bcp_image_fds; std::vector bcp_oat_fds; std::vector bcp_vdex_fds; std::vector> opened_files; bool added_any = false; std::string artifact_dir; for (size_t i = 0; i < bcp_jars.size(); i++) { const std::string& jar = bcp_jars[i]; std::string basename = GetBootImageComponentBasename(jar, /*is_first_jar=*/i == 0); // If there is an entry in `boot_image_locations` for the current jar, update `artifact_dir` for // the current jar and the subsequent jars. for (const std::string& location : boot_image_locations) { if (Basename(location) == basename) { artifact_dir = Dirname(location); break; } } CHECK(!artifact_dir.empty()); std::string image_path = ART_FORMAT("{}/{}", artifact_dir, basename); image_path = GetSystemImageFilename(image_path.c_str(), isa); std::unique_ptr image_file(OS::OpenFileForReading(image_path.c_str())); if (image_file != nullptr) { bcp_image_fds.push_back(std::to_string(image_file->Fd())); opened_files.push_back(std::move(image_file)); added_any = true; } else if (errno == ENOENT) { bcp_image_fds.push_back("-1"); } else { return ErrnoErrorf("Failed to open boot image file '{}'", image_path); } std::string oat_path = ReplaceFileExtension(image_path, "oat"); std::unique_ptr oat_file(OS::OpenFileForReading(oat_path.c_str())); if (oat_file != nullptr) { bcp_oat_fds.push_back(std::to_string(oat_file->Fd())); opened_files.push_back(std::move(oat_file)); added_any = true; } else if (errno == ENOENT) { bcp_oat_fds.push_back("-1"); } else { return ErrnoErrorf("Failed to open boot image file '{}'", oat_path); } std::string vdex_path = ReplaceFileExtension(image_path, "vdex"); std::unique_ptr vdex_file(OS::OpenFileForReading(vdex_path.c_str())); if (vdex_file != nullptr) { bcp_vdex_fds.push_back(std::to_string(vdex_file->Fd())); opened_files.push_back(std::move(vdex_file)); added_any = true; } else if (errno == ENOENT) { bcp_vdex_fds.push_back("-1"); } else { return ErrnoErrorf("Failed to open boot image file '{}'", vdex_path); } } // Add same amount of FDs as BCP JARs, or none. if (added_any) { std::move(opened_files.begin(), opened_files.end(), std::back_inserter(output_files)); args.AddRuntime("-Xbootclasspathimagefds:%s", Join(bcp_image_fds, ':')); args.AddRuntime("-Xbootclasspathoatfds:%s", Join(bcp_oat_fds, ':')); args.AddRuntime("-Xbootclasspathvdexfds:%s", Join(bcp_vdex_fds, ':')); } return {}; } std::string GetStagingLocation(const std::string& staging_dir, const std::string& path) { return staging_dir + "/" + Basename(path); } 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; uint64_t bytes_available; const std::string& art_apex_data_path = GetArtApexData(); if (!GetFreeSpace(art_apex_data_path, &bytes_available)) { return false; } if (bytes_available < kMinimumSpaceForCompilation) { LOG(WARNING) << "Low space for " << QuotePath(art_apex_data_path) << " (" << bytes_available << " bytes)"; return false; } return true; } 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. static const bool 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; } OdrMetrics::BcpCompilationType BootImages::GetTypeForMetrics() const { if (primary_boot_image && boot_image_mainline_extension) { return OdrMetrics::BcpCompilationType::kPrimaryAndMainline; } if (boot_image_mainline_extension) { return OdrMetrics::BcpCompilationType::kMainline; } LOG(FATAL) << "Unexpected BCP compilation type"; UNREACHABLE(); } int CompilationOptions::CompilationUnitCount() const { int count = 0; for (const auto& [isa, boot_images] : boot_images_to_generate_for_isas) { count += boot_images.Count(); } count += system_server_jars_to_compile.size(); return count; } OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config) : OnDeviceRefresh(config, config.GetArtifactDirectory() + "/" + kCacheInfoFile, std::make_unique(), CheckCompilationSpace, setfilecon) {} OnDeviceRefresh::OnDeviceRefresh( const OdrConfig& config, const std::string& cache_info_filename, std::unique_ptr exec_utils, android::base::function_ref check_compilation_space, android::base::function_ref setfilecon) : 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) { // 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(), ":"); all_systemserver_jars_ = Split(config_.GetSystemServerClasspath(), ":"); systemserver_classpath_jars_ = {all_systemserver_jars_.begin(), all_systemserver_jars_.end()}; boot_classpath_jars_ = Split(config_.GetBootClasspath(), ":"); std::string standalone_system_server_jars_str = config_.GetStandaloneSystemServerJars(); if (!standalone_system_server_jars_str.empty()) { std::vector standalone_systemserver_jars = Split(standalone_system_server_jars_str, ":"); std::move(standalone_systemserver_jars.begin(), standalone_systemserver_jars.end(), std::back_inserter(all_systemserver_jars_)); } } time_t OnDeviceRefresh::GetExecutionTimeUsed() const { return time(nullptr) - start_time_; } time_t OnDeviceRefresh::GetExecutionTimeRemaining() const { return std::max(static_cast(0), kMaximumExecutionSeconds - GetExecutionTimeUsed()); } time_t OnDeviceRefresh::GetSubprocessTimeout() const { return std::min(GetExecutionTimeRemaining(), kMaxChildProcessSeconds); } Result OnDeviceRefresh::CreateStagingDirectory() const { std::string staging_dir = GetArtApexData() + "/staging"; 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); } return staging_dir; } std::optional> OnDeviceRefresh::GetApexInfoList() const { std::optional info_list = apex::readApexInfoList(config_.GetApexInfoListFile().c_str()); if (!info_list.has_value()) { return std::nullopt; } // We are only interested in active APEXes that contain compilable JARs. std::unordered_set relevant_apexes; relevant_apexes.reserve(info_list->getApexInfo().size()); for (const std::vector* 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"); std::vector filtered_info_list; std::copy_if(info_list->getApexInfo().begin(), info_list->getApexInfo().end(), std::back_inserter(filtered_info_list), [&](const apex::ApexInfo& info) { return info.getIsActive() && relevant_apexes.count(info.getModuleName()) != 0; }); return filtered_info_list; } Result OnDeviceRefresh::ReadCacheInfo() const { std::optional 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 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 system_properties; for (const auto& [key, value] : config_.GetSystemProperties()) { if (!art::ContainsElement(kIgnoredSystemProperties, key)) { system_properties.emplace_back(key, value); } } std::optional> apex_info_list = GetApexInfoList(); if (!apex_info_list.has_value()) { return Errorf("Could not update {}: no APEX info", QuotePath(cache_info_filename_)); } std::optional 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_)); } art_apex::ModuleInfo art_module_info = GenerateModuleInfo(art_apex_info.value()); std::vector module_info_list = GenerateModuleInfoList(apex_info_list.value()); std::vector bcp_components = GenerateBootClasspathComponents(); std::vector dex2oat_bcp_components = GenerateDex2oatBootClasspathComponents(); std::vector system_server_components = GenerateSystemServerComponents(); std::ofstream out(cache_info_filename_.c_str()); if (out.fail()) { return ErrnoErrorf("Could not create cache info file {}", QuotePath(cache_info_filename_)); } std::unique_ptr info(new art_apex::CacheInfo( {art_apex::KeyValuePairList(system_properties)}, {art_module_info}, {art_apex::ModuleInfoList(module_info_list)}, {art_apex::Classpath(bcp_components)}, {art_apex::Classpath(dex2oat_bcp_components)}, {art_apex::SystemServerComponents(system_server_components)}, config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt)); art_apex::write(out, *info); out.close(); if (out.fail()) { return ErrnoErrorf("Could not write cache info file {}", QuotePath(cache_info_filename_)); } return {}; } static void 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 = (90 * current_compilation) / number_of_compilations; SetProperty("service.bootanim.progress", std::to_string(value)); } std::vector OnDeviceRefresh::GenerateBootClasspathComponents() const { return GenerateComponents(boot_classpath_jars_); } std::vector OnDeviceRefresh::GenerateDex2oatBootClasspathComponents() const { return GenerateComponents(dex2oat_boot_classpath_jars_); } std::vector OnDeviceRefresh::GenerateSystemServerComponents() const { return GenerateComponents( all_systemserver_jars_, [&](const std::string& path, uint64_t size, const std::string& checksum) { bool isInClasspath = ContainsElement(systemserver_classpath_jars_, path); return art_apex::SystemServerComponent{path, size, checksum, isInClasspath}; }); } std::vector OnDeviceRefresh::GetArtBcpJars() const { std::string art_root = GetArtRoot() + "/"; std::vector 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 OnDeviceRefresh::GetFrameworkBcpJars() const { std::string art_root = GetArtRoot() + "/"; std::vector 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 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::string OnDeviceRefresh::GetPrimaryBootImage(bool on_system, bool minimal) const { DCHECK(!on_system || !minimal); const char* basename = minimal ? kMinimalBootImageBasename : kFirstBootImageBasename; if (on_system) { // Typically "/system/framework/boot.art". return GetPrebuiltPrimaryBootImageDir() + "/" + basename; } else { // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot.art". return config_.GetArtifactDirectory() + "/" + basename; } } std::string OnDeviceRefresh::GetPrimaryBootImagePath(bool on_system, bool minimal, InstructionSet isa) const { // Typically "/data/misc/apexdata/com.android.art/dalvik-cache//boot.art". return GetSystemImageFilename(GetPrimaryBootImage(on_system, minimal).c_str(), isa); } std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtension() const { std::vector framework_bcp_jars = GetFrameworkBcpJars(); std::string basename = GetBootImageComponentBasename(framework_bcp_jars[0], /*is_first_jar=*/false); // Typically "/system/framework/boot-framework.art". return ART_FORMAT("{}/framework/{}", GetAndroidRoot(), basename); } std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtensionPath(InstructionSet isa) const { // Typically "/system/framework//boot-framework.art". return GetSystemImageFilename(GetSystemBootImageFrameworkExtension().c_str(), isa); } std::string OnDeviceRefresh::GetBootImageMainlineExtension(bool on_system) const { std::vector mainline_bcp_jars = GetMainlineBcpJars(); std::string basename = GetBootImageComponentBasename(mainline_bcp_jars[0], /*is_first_jar=*/false); if (on_system) { // Typically "/system/framework/boot-framework-adservices.art". return ART_FORMAT("{}/framework/{}", GetAndroidRoot(), basename); } else { // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework-adservices.art". return ART_FORMAT("{}/{}", config_.GetArtifactDirectory(), basename); } } std::string OnDeviceRefresh::GetBootImageMainlineExtensionPath(bool on_system, InstructionSet isa) const { // Typically // "/data/misc/apexdata/com.android.art/dalvik-cache//boot-framework-adservices.art". return GetSystemImageFilename(GetBootImageMainlineExtension(on_system).c_str(), isa); } std::vector OnDeviceRefresh::GetBestBootImages(InstructionSet isa, bool include_mainline_extension) const { std::vector 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; } std::string OnDeviceRefresh::GetSystemServerImagePath(bool on_system, const std::string& jar_path) const { if (on_system) { if (LocationIsOnApex(jar_path)) { return GetSystemOdexFilenameForApex(jar_path, config_.GetSystemServerIsa()); } std::string jar_name = Basename(jar_path); std::string image_name = ReplaceFileExtension(jar_name, "art"); const char* isa_str = GetInstructionSetString(config_.GetSystemServerIsa()); // Typically "/system/framework/oat//services.art". return ART_FORMAT("{}/oat/{}/{}", Dirname(jar_path), isa_str, image_name); } else { // Typically // "/data/misc/apexdata/.../dalvik-cache//system@framework@services.jar@classes.art". const std::string image = GetApexDataImage(jar_path); return GetSystemImageFilename(image.c_str(), config_.GetSystemServerIsa()); } } WARN_UNUSED bool OnDeviceRefresh::RemoveArtifactsDirectory() const { if (config_.GetDryRun()) { LOG(INFO) << "Directory " << QuotePath(config_.GetArtifactDirectory()) << " and contents would be removed (dry-run)."; return true; } return RemoveDirectory(config_.GetArtifactDirectory()); } WARN_UNUSED bool OnDeviceRefresh::PrimaryBootImageExist( bool on_system, bool minimal, InstructionSet isa, /*out*/ std::string* error_msg, /*out*/ std::vector* 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)) { return false; } // 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)) { return false; } } return true; } WARN_UNUSED bool OnDeviceRefresh::BootImageMainlineExtensionExist( bool on_system, InstructionSet isa, /*out*/ std::string* error_msg, /*out*/ std::vector* checked_artifacts) const { std::string path = GetBootImageMainlineExtensionPath(on_system, isa); OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path); return ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts); } bool OnDeviceRefresh::SystemServerArtifactsExist( bool on_system, /*out*/ std::string* error_msg, /*out*/ std::set* jars_missing_artifacts, /*out*/ std::vector* 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. const bool 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; } } 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."); })); const OdrSystemProperties& system_properties = config_.GetSystemProperties(); for (const SystemPropertyConfig& system_property_config : *kSystemProperties.get()) { std::string property = system_properties.GetOrEmpty(system_property_config.name); DCHECK_NE(property, ""); if (property != system_property_config.default_value) { LOG(INFO) << "System property " << system_property_config.name << " has a non-default value (" << property << ")."; return false; } } return true; } WARN_UNUSED bool OnDeviceRefresh::CheckSystemPropertiesHaveNotChanged( const art_apex::CacheInfo& cache_info) const { std::unordered_map cached_system_properties; std::unordered_set checked_properties; 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."; return false; } for (const art_apex::KeyValuePair& pair : list->getItem()) { cached_system_properties[pair.getK()] = pair.getV(); checked_properties.insert(pair.getK()); } const OdrSystemProperties& system_properties = config_.GetSystemProperties(); for (const auto& [key, value] : system_properties) { if (!art::ContainsElement(kIgnoredSystemProperties, key)) { checked_properties.insert(key); } } for (const std::string& name : checked_properties) { std::string property = system_properties.GetOrEmpty(name); std::string cached_property = cached_system_properties[name]; if (property != cached_property) { LOG(INFO) << "System property " << name << " value changed (before: \"" << cached_property << "\", now: \"" << property << "\")."; return false; } } return true; } WARN_UNUSED bool OnDeviceRefresh::CheckBuildUserfaultFdGc() const { bool build_enable_uffd_gc = config_.GetSystemProperties().GetBool("ro.dalvik.vm.enable_uffd_gc", /*default_value=*/false); bool is_at_most_u = !IsAtLeastV(); bool kernel_supports_uffd = KernelSupportsUffd(); if (!art::odrefresh::CheckBuildUserfaultFdGc( build_enable_uffd_gc, is_at_most_u, kernel_supports_uffd)) { // Normally, this should not happen. If this happens, the system image was probably built with a // wrong PRODUCT_ENABLE_UFFD_GC flag. LOG(WARNING) << ART_FORMAT( "Userfaultfd GC check failed (build_enable_uffd_gc: {}, is_at_most_u: {}, " "kernel_supports_uffd: {}).", build_enable_uffd_gc, is_at_most_u, kernel_supports_uffd); return false; } return true; } WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForSystem( const std::vector& apex_info_list) const { if (!CheckSystemPropertiesAreDefault()) { return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } if (!CheckBuildUserfaultFdGc()) { return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } std::optional 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 (std::any_of(apex_info_list.begin(), apex_info_list.end(), [](const apex::ApexInfo& apex_info) { return !apex_info.getIsFactory(); })) { LOG(INFO) << "Updated APEXes mounted"; return PreconditionCheckResult::BootImageMainlineExtensionNotOk( OdrMetrics::Trigger::kApexVersionMismatch); } return PreconditionCheckResult::AllOk(); } WARN_UNUSED static bool CheckModuleInfo(const art_apex::ModuleInfo& cached_info, const apex::ApexInfo& current_info) { if (cached_info.getVersionCode() != current_info.getVersionCode()) { LOG(INFO) << ART_FORMAT("APEX ({}) version code mismatch (before: {}, now: {})", current_info.getModuleName(), cached_info.getVersionCode(), current_info.getVersionCode()); return false; } 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()); return false; } // 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()); return false; } return true; } WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForData( const std::vector& apex_info_list) const { Result 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 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 current_dex2oat_bcp_components = GenerateDex2oatBootClasspathComponents(); const art_apex::Classpath* cached_dex2oat_bcp_components = cache_info->getFirstDex2oatBootClasspath(); if (cached_dex2oat_bcp_components == nullptr) { LOG(INFO) << "Missing Dex2oatBootClasspath components."; return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } Result 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); } std::unordered_map cached_module_info_map; for (const art_apex::ModuleInfo& module_info : cached_module_info_list->getModuleInfo()) { cached_module_info_map[module_info.getName()] = &module_info; } // 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); } const art_apex::ModuleInfo* cached_module_info = it->second; if (!CheckModuleInfo(*cached_module_info, current_apex_info)) { return PreconditionCheckResult::BootImageMainlineExtensionNotOk( OdrMetrics::Trigger::kApexVersionMismatch); } } const std::vector current_bcp_components = GenerateBootClasspathComponents(); const art_apex::Classpath* cached_bcp_components = cache_info->getFirstBootClasspath(); if (cached_bcp_components == nullptr) { LOG(INFO) << "Missing BootClasspath components."; 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 current_system_server_components = GenerateSystemServerComponents(); const art_apex::SystemServerComponents* cached_system_server_components = cache_info->getFirstSystemServerComponents(); if (cached_system_server_components == nullptr) { LOG(INFO) << "Missing SystemServerComponents."; return PreconditionCheckResult::SystemServerNotOk(OdrMetrics::Trigger::kApexVersionMismatch); } result = CheckSystemServerComponents(current_system_server_components, cached_system_server_components->getComponent()); if (!result.ok()) { LOG(INFO) << "SystemServerComponents mismatch: " << result.error(); return PreconditionCheckResult::SystemServerNotOk(OdrMetrics::Trigger::kDexFilesChanged); } return PreconditionCheckResult::AllOk(); } WARN_UNUSED BootImages OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate( OdrMetrics& metrics, InstructionSet isa, const PreconditionCheckResult& system_result, const PreconditionCheckResult& data_result, /*out*/ std::vector* checked_artifacts) const { const char* isa_str = GetInstructionSetString(isa); 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}; } LOG(INFO) << ART_FORMAT("Checking boot images /data ({})", isa_str); BootImages boot_images_on_data{.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()); } } BootImages boot_images_to_generate{ .primary_boot_image = !boot_images_on_system.primary_boot_image && !boot_images_on_data.primary_boot_image, .boot_image_mainline_extension = !boot_images_on_system.boot_image_mainline_extension && !boot_images_on_data.boot_image_mainline_extension, }; 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 OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate( OdrMetrics& metrics, const PreconditionCheckResult& system_result, const PreconditionCheckResult& data_result, /*out*/ std::vector* checked_artifacts) const { std::set jars_to_compile; std::set 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(); } std::set jars_missing_artifacts_on_data; std::string error_msg; if (data_result.IsSystemServerOk()) { SystemServerArtifactsExist( /*on_system=*/false, &error_msg, &jars_missing_artifacts_on_data, checked_artifacts); } else { jars_missing_artifacts_on_data = AllSystemServerJars(); } std::set_intersection(jars_missing_artifacts_on_system.begin(), jars_missing_artifacts_on_system.end(), jars_missing_artifacts_on_data.begin(), jars_missing_artifacts_on_data.end(), std::inserter(jars_to_compile, jars_to_compile.end())); if (!jars_to_compile.empty()) { if (data_result.IsSystemServerOk()) { LOG(INFO) << "Incomplete system_server artifacts on /data: " << error_msg; metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); } else { metrics.SetTrigger(data_result.GetTrigger()); } return jars_to_compile; } LOG(INFO) << "system_server artifacts on /data OK"; return {}; } Result OnDeviceRefresh::CleanupArtifactDirectory( OdrMetrics& metrics, const std::vector& artifacts_to_keep) const { const std::string& artifact_dir = config_.GetArtifactDirectory(); std::unordered_set artifact_set{artifacts_to_keep.begin(), artifacts_to_keep.end()}; // 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 entries; std::error_code ec; for (const auto& 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)); } } } else if (!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)); } } } remove_artifact_dir.Disable(); return {}; } Result OnDeviceRefresh::RefreshExistingArtifacts() const { const std::string& artifact_dir = config_.GetArtifactDirectory(); if (!OS::DirectoryExists(artifact_dir.c_str())) { return {}; } std::vector entries; std::error_code ec; for (const auto& 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)); } } } return {}; } WARN_UNUSED ExitCode OnDeviceRefresh::CheckArtifactsAreUpToDate(OdrMetrics& metrics, /*out*/ CompilationOptions* compilation_options) const { metrics.SetStage(OdrMetrics::Stage::kCheck); // 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> 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 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()); InstructionSet system_server_isa = config_.GetSystemServerIsa(); std::vector checked_artifacts; PreconditionCheckResult system_result = CheckPreconditionForSystem(apex_info_list.value()); PreconditionCheckResult data_result = CheckPreconditionForData(apex_info_list.value()); for (InstructionSet isa : config_.GetBootClasspathIsas()) { BootImages boot_images_to_generate = CheckBootClasspathArtifactsAreUpToDate( metrics, isa, system_result, data_result, &checked_artifacts); if (boot_images_to_generate.Count() > 0) { compilation_options->boot_images_to_generate_for_isas.emplace_back(isa, boot_images_to_generate); // system_server artifacts are invalid without valid boot classpath artifacts. if (isa == system_server_isa) { compilation_options->system_server_jars_to_compile = AllSystemServerJars(); } } } if (compilation_options->system_server_jars_to_compile.empty()) { compilation_options->system_server_jars_to_compile = CheckSystemServerArtifactsAreUpToDate( metrics, system_result, data_result, &checked_artifacts); } bool compilation_required = compilation_options->CompilationUnitCount() > 0; 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 result = CleanupArtifactDirectory(metrics, checked_artifacts); if (!result.ok()) { LOG(ERROR) << result.error(); return ExitCode::kCleanupFailed; } return compilation_required ? ExitCode::kCompilationRequired : ExitCode::kOkay; } WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oat( const std::string& staging_dir, const std::string& debug_message, InstructionSet isa, const std::vector& dex_files, const std::vector& boot_classpath, const std::vector& input_boot_images, const OdrArtifacts& artifacts, CmdlineBuilder&& extra_args, /*inout*/ std::vector>& readonly_files_raii) const { CmdlineBuilder args; args.Add(config_.GetDex2Oat()); AddDex2OatCommonOptions(args); AddDex2OatDebugInfo(args); AddDex2OatInstructionSet(args, isa, config_.GetSystemProperties()); Result 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(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()); } } args.Add("--oat-location=%s", artifacts.OatPath()); std::pair location_kind_pairs[] = { std::make_pair(artifacts.ImagePath(), artifacts.ImageKind()), std::make_pair(artifacts.OatPath(), "oat"), std::make_pair(artifacts.VdexPath(), "output-vdex")}; std::string install_location = Dirname(artifacts.OatPath()); if (!EnsureDirectoryExists(install_location)) { return CompilationResult::Error( OdrMetrics::Status::kIoError, ART_FORMAT("Error encountered when preparing directory '{}'", install_location)); } std::vector> output_files; for (const auto& [location, kind] : location_kind_pairs) { std::string output_location = staging_dir.empty() ? location : GetStagingLocation(staging_dir, location); std::unique_ptr output_file(OS::CreateEmptyFile(output_location.c_str())); if (output_file == nullptr) { return CompilationResult::Error( OdrMetrics::Status::kIoError, ART_FORMAT("Failed to create {} file '{}': {}", kind, output_location, strerror(errno))); } args.Add(StringPrintf("--%s-fd=%d", kind, output_file->Fd())); output_files.emplace_back(std::move(output_file)); } // We don't care about file state on failure. auto cleanup = ScopeGuard([&] { for (const std::unique_ptr& file : output_files) { file->MarkUnchecked(); } }); args.Concat(std::move(extra_args)); Timer timer; time_t timeout = GetSubprocessTimeout(); std::string cmd_line = Join(args.Get(), ' '); LOG(INFO) << ART_FORMAT("{}: {} [timeout {}s]", debug_message, cmd_line, timeout); if (config_.GetDryRun()) { LOG(INFO) << "Compilation skipped (dry-run)."; return CompilationResult::Ok(); } std::string error_msg; ExecResult dex2oat_result = exec_utils_->ExecAndReturnResult(args.Get(), timeout, &error_msg); if (dex2oat_result.exit_code != 0) { return CompilationResult::Dex2oatError( dex2oat_result.exit_code < 0 ? error_msg : ART_FORMAT("dex2oat returned an unexpected code: {}", dex2oat_result.exit_code), timer.duration().count(), dex2oat_result); } if (staging_dir.empty()) { for (const std::unique_ptr& file : output_files) { if (file->FlushCloseOrErase() != 0) { return CompilationResult::Error( OdrMetrics::Status::kIoError, ART_FORMAT("Failed to flush close file '{}'", file->GetPath())); } } } else { for (const std::unique_ptr& file : output_files) { if (file->Flush() != 0) { return CompilationResult::Error(OdrMetrics::Status::kIoError, ART_FORMAT("Failed to flush file '{}'", file->GetPath())); } } if (!MoveOrEraseFiles(output_files, install_location)) { return CompilationResult::Error( OdrMetrics::Status::kIoError, ART_FORMAT("Failed to commit artifacts to '{}'", install_location)); } } cleanup.Disable(); return CompilationResult::Dex2oatOk(timer.duration().count(), dex2oat_result); } WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oatForBootClasspath(const std::string& staging_dir, const std::string& debug_name, InstructionSet isa, const std::vector& dex_files, const std::vector& boot_classpath, const std::vector& input_boot_images, const std::string& output_path) const { CmdlineBuilder args; std::vector> readonly_files_raii; // Compile as a single image for fewer files and slightly less memory overhead. args.Add("--single-image"); if (input_boot_images.empty()) { // Primary boot image. std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof"; std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof"; Result has_any_profile = AddDex2OatProfile( args, readonly_files_raii, {art_boot_profile_file, framework_boot_profile_file}); if (!has_any_profile.ok()) { return CompilationResult::Error(OdrMetrics::Status::kIoError, has_any_profile.error().message()); } if (!*has_any_profile) { return CompilationResult::Error(OdrMetrics::Status::kIoError, "Missing boot image profile"); } const std::string& compiler_filter = config_.GetBootImageCompilerFilter(); if (!compiler_filter.empty()) { args.Add("--compiler-filter=%s", compiler_filter); } else { args.Add("--compiler-filter=%s", kPrimaryCompilerFilter); } args.Add(StringPrintf("--base=0x%08x", ART_BASE_ADDRESS)); std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects"); std::unique_ptr file(OS::OpenFileForReading(dirty_image_objects_file.c_str())); if (file != nullptr) { args.Add("--dirty-image-objects-fd=%d", file->Fd()); readonly_files_raii.push_back(std::move(file)); } else if (errno == ENOENT) { LOG(WARNING) << ART_FORMAT("Missing dirty objects file '{}'", dirty_image_objects_file); } else { return CompilationResult::Error(OdrMetrics::Status::kIoError, ART_FORMAT("Failed to open dirty objects file '{}': {}", dirty_image_objects_file, strerror(errno))); } std::string preloaded_classes_file(GetAndroidRoot() + "/etc/preloaded-classes"); file.reset(OS::OpenFileForReading(preloaded_classes_file.c_str())); if (file != nullptr) { args.Add("--preloaded-classes-fds=%d", file->Fd()); readonly_files_raii.push_back(std::move(file)); } else if (errno == ENOENT) { LOG(WARNING) << ART_FORMAT("Missing preloaded classes file '{}'", preloaded_classes_file); } else { return CompilationResult::Error(OdrMetrics::Status::kIoError, ART_FORMAT("Failed to open preloaded classes file '{}': {}", preloaded_classes_file, strerror(errno))); } } else { // Mainline extension. args.Add("--compiler-filter=%s", kMainlineCompilerFilter); } const OdrSystemProperties& system_properties = config_.GetSystemProperties(); args.AddRuntimeIfNonEmpty("-Xms%s", system_properties.GetOrEmpty("dalvik.vm.image-dex2oat-Xms")) .AddRuntimeIfNonEmpty("-Xmx%s", system_properties.GetOrEmpty("dalvik.vm.image-dex2oat-Xmx")); return RunDex2oat( staging_dir, ART_FORMAT("Compiling boot classpath ({}, {})", GetInstructionSetString(isa), debug_name), isa, dex_files, boot_classpath, input_boot_images, OdrArtifacts::ForBootImage(output_path), std::move(args), readonly_files_raii); } WARN_UNUSED CompilationResult OnDeviceRefresh::CompileBootClasspath(const std::string& staging_dir, InstructionSet isa, BootImages boot_images, const std::function& on_dex2oat_success) const { DCHECK_GT(boot_images.Count(), 0); DCHECK_IMPLIES(boot_images.primary_boot_image, boot_images.boot_image_mainline_extension); 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 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; } return result; } WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oatForSystemServer( const std::string& staging_dir, const std::string& dex_file, const std::vector& classloader_context) const { CmdlineBuilder args; std::vector> readonly_files_raii; InstructionSet isa = config_.GetSystemServerIsa(); std::string output_path = GetSystemServerImagePath(/*on_system=*/false, dex_file); std::string actual_jar_path = RewriteParentDirectoryIfNeeded(dex_file); std::string profile = actual_jar_path + ".prof"; const std::string& compiler_filter = config_.GetSystemServerCompilerFilter(); bool maybe_add_profile = !compiler_filter.empty() || HasVettedDeviceSystemServerProfiles(); bool has_added_profile = false; if (maybe_add_profile) { Result has_any_profile = AddDex2OatProfile(args, readonly_files_raii, {profile}); if (!has_any_profile.ok()) { return CompilationResult::Error(OdrMetrics::Status::kIoError, has_any_profile.error().message()); } has_added_profile = *has_any_profile; } if (!compiler_filter.empty()) { args.Add("--compiler-filter=%s", compiler_filter); } else if (has_added_profile) { args.Add("--compiler-filter=speed-profile"); } else { args.Add("--compiler-filter=speed"); } std::string context_path = Join(classloader_context, ':'); if (art::ContainsElement(systemserver_classpath_jars_, dex_file)) { args.Add("--class-loader-context=PCL[%s]", context_path); } else { args.Add("--class-loader-context=PCL[];PCL[%s]", context_path); } if (!classloader_context.empty()) { std::vector fds; for (const std::string& path : classloader_context) { std::string actual_path = RewriteParentDirectoryIfNeeded(path); std::unique_ptr file(OS::OpenFileForReading(actual_path.c_str())); if (file == nullptr) { return CompilationResult::Error( OdrMetrics::Status::kIoError, ART_FORMAT( "Failed to open classloader context '{}': {}", actual_path, strerror(errno))); } fds.emplace_back(file->Fd()); readonly_files_raii.emplace_back(std::move(file)); } args.Add("--class-loader-context-fds=%s", Join(fds, ':')); } const OdrSystemProperties& system_properties = config_.GetSystemProperties(); args.AddRuntimeIfNonEmpty("-Xms%s", system_properties.GetOrEmpty("dalvik.vm.dex2oat-Xms")) .AddRuntimeIfNonEmpty("-Xmx%s", system_properties.GetOrEmpty("dalvik.vm.dex2oat-Xmx")); return RunDex2oat(staging_dir, ART_FORMAT("Compiling {}", Basename(dex_file)), isa, {dex_file}, boot_classpath_jars_, GetBestBootImages(isa, /*include_mainline_extension=*/true), OdrArtifacts::ForSystemServer(output_path), std::move(args), readonly_files_raii); } WARN_UNUSED CompilationResult OnDeviceRefresh::CompileSystemServer(const std::string& staging_dir, const std::set& system_server_jars_to_compile, const std::function& on_dex2oat_success) const { DCHECK(!system_server_jars_to_compile.empty()); CompilationResult result = CompilationResult::Ok(); std::vector classloader_context; if (!check_compilation_space_()) { LOG(ERROR) << "Compilation of system_server failed: Insufficient space"; return CompilationResult::Error(OdrMetrics::Status::kNoSpace, "Insufficient space"); } for (const std::string& jar : all_systemserver_jars_) { if (ContainsElement(system_server_jars_to_compile, jar)) { CompilationResult current_result = RunDex2oatForSystemServer(staging_dir, jar, classloader_context); result.Merge(current_result); if (current_result.IsOk()) { on_dex2oat_success(); } else { LOG(ERROR) << ART_FORMAT("Compilation of {} failed: {}", Basename(jar), result.error_msg); } } if (ContainsElement(systemserver_classpath_jars_, jar)) { classloader_context.emplace_back(jar); } } return result; } WARN_UNUSED ExitCode OnDeviceRefresh::Compile(OdrMetrics& metrics, CompilationOptions compilation_options) const { std::string staging_dir; metrics.SetStage(OdrMetrics::Stage::kPreparation); // 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 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 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 res = CreateStagingDirectory(); if (!res.ok()) { LOG(ERROR) << res.error().message(); metrics.SetStatus(OdrMetrics::Status::kStagingFailed); return ExitCode::kCleanupFailed; } staging_dir = res.value(); } uint32_t dex2oat_invocation_count = 0; uint32_t total_dex2oat_invocation_count = compilation_options.CompilationUnitCount(); ReportNextBootAnimationProgress(dex2oat_invocation_count, total_dex2oat_invocation_count); auto advance_animation_progress = [&]() { ReportNextBootAnimationProgress(++dex2oat_invocation_count, total_dex2oat_invocation_count); }; const std::vector& bcp_instruction_sets = config_.GetBootClasspathIsas(); DCHECK(!bcp_instruction_sets.empty() && bcp_instruction_sets.size() <= 2); InstructionSet system_server_isa = config_.GetSystemServerIsa(); bool system_server_isa_failed = false; std::optional> first_failure; for (const auto& [isa, boot_images_to_generate] : compilation_options.boot_images_to_generate_for_isas) { OdrMetrics::Stage stage = (isa == bcp_instruction_sets.front()) ? OdrMetrics::Stage::kPrimaryBootClasspath : OdrMetrics::Stage::kSecondaryBootClasspath; CompilationResult bcp_result = CompileBootClasspath(staging_dir, isa, boot_images_to_generate, advance_animation_progress); metrics.SetDex2OatResult(stage, bcp_result.elapsed_time_ms, bcp_result.dex2oat_result); metrics.SetBcpCompilationType(stage, boot_images_to_generate.GetTypeForMetrics()); if (!bcp_result.IsOk()) { if (isa == system_server_isa) { system_server_isa_failed = true; } first_failure = first_failure.value_or(std::make_pair(stage, bcp_result.status)); } } // Don't compile system server if the compilation of BCP failed. if (!system_server_isa_failed && !compilation_options.system_server_jars_to_compile.empty() && !config_.GetOnlyBootImages()) { OdrMetrics::Stage stage = OdrMetrics::Stage::kSystemServerClasspath; CompilationResult ss_result = CompileSystemServer( staging_dir, compilation_options.system_server_jars_to_compile, advance_animation_progress); metrics.SetDex2OatResult(stage, ss_result.elapsed_time_ms, ss_result.dex2oat_result); if (!ss_result.IsOk()) { first_failure = first_failure.value_or(std::make_pair(stage, ss_result.status)); } } if (first_failure.has_value()) { LOG(ERROR) << "Compilation failed, stage: " << first_failure->first << " status: " << first_failure->second; metrics.SetStage(first_failure->first); metrics.SetStatus(first_failure->second); if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) { return ExitCode::kCleanupFailed; } return ExitCode::kCompilationFailed; } metrics.SetStage(OdrMetrics::Stage::kComplete); metrics.SetStatus(OdrMetrics::Status::kOK); return ExitCode::kCompilationSuccess; } } // namespace odrefresh } // namespace art