| /* |
| * 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 <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sysexits.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cerrno> |
| #include <cstdarg> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <filesystem> |
| #include <fstream> |
| #include <functional> |
| #include <initializer_list> |
| #include <iosfwd> |
| #include <iostream> |
| #include <iterator> |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <set> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <system_error> |
| #include <type_traits> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #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 "palette/palette.h" |
| #include "palette/palette_types.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::SetProperty; |
| using ::android::base::Split; |
| using ::android::base::StartsWith; |
| using ::android::base::StringPrintf; |
| using ::android::base::Timer; |
| using ::android::modules::sdklevel::IsAtLeastT; |
| using ::android::modules::sdklevel::IsAtLeastU; |
| 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<std::unique_ptr<File>>& 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<std::unique_ptr<File>>& files, |
| std::string_view output_directory_path) { |
| 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(); |
| |
| 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<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 (StartsWith(path, "/system/")) { |
| return GetAndroidRoot() + path.substr(7); |
| } else if (StartsWith(path, "/system_ext/")) { |
| return GetSystemExtRoot() + path.substr(11); |
| } else { |
| return path; |
| } |
| } |
| |
| template <typename T> |
| Result<void> CheckComponents( |
| const std::vector<T>& expected_components, |
| const std::vector<T>& actual_components, |
| const std::function<Result<void>(const T& expected, const T& actual)>& custom_checker = |
| [](const T&, const T&) -> Result<void> { 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<void> result = custom_checker(expected, actual); |
| if (!result.ok()) { |
| return Errorf("Component {} {}", i, result.error().message()); |
| } |
| } |
| |
| return {}; |
| } |
| |
| Result<void> CheckSystemServerComponents( |
| const std::vector<art_apex::SystemServerComponent>& expected_components, |
| const std::vector<art_apex::SystemServerComponent>& actual_components) { |
| return CheckComponents<art_apex::SystemServerComponent>( |
| expected_components, |
| actual_components, |
| [](const art_apex::SystemServerComponent& expected, |
| const art_apex::SystemServerComponent& actual) -> Result<void> { |
| if (expected.getIsInClasspath() != actual.getIsInClasspath()) { |
| return Errorf("isInClasspath differs ({} != {})", |
| expected.getIsInClasspath(), |
| actual.getIsInClasspath()); |
| } |
| |
| return {}; |
| }); |
| } |
| |
| template <typename T> |
| std::vector<T> GenerateComponents( |
| const std::vector<std::string>& jars, |
| const std::function<T(const std::string& path, uint64_t size, const std::string& checksum)>& |
| custom_generator) { |
| std::vector<T> 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<uint32_t> 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<T> component = custom_generator(path, static_cast<uint64_t>(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<art_apex::Component> GenerateComponents(const std::vector<std::string>& jars) { |
| return GenerateComponents<art_apex::Component>( |
| 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<std::string>* checked_artifacts = nullptr) { |
| std::vector<const char*> 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<void> 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<bool> AddDex2OatProfile( |
| /*inout*/ CmdlineBuilder& args, |
| /*inout*/ std::vector<std::unique_ptr<File>>& output_files, |
| const std::vector<std::string>& profile_paths) { |
| bool has_any_profile = false; |
| 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)); |
| has_any_profile = true; |
| } else if (errno != ENOENT) { |
| return ErrnoErrorf("Failed to open profile file '{}'", path); |
| } |
| } |
| return has_any_profile; |
| } |
| |
| 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 (StartsWith(jar, "/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_file); |
| } |
| |
| 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<void> AddCompiledBootClasspathFdsIfAny( |
| /*inout*/ CmdlineBuilder& args, |
| /*inout*/ std::vector<std::unique_ptr<File>>& output_files, |
| const std::vector<std::string>& bcp_jars, |
| InstructionSet isa, |
| const std::vector<std::string>& boot_image_locations) { |
| std::vector<std::string> bcp_image_fds; |
| std::vector<std::string> bcp_oat_fds; |
| std::vector<std::string> bcp_vdex_fds; |
| std::vector<std::unique_ptr<File>> 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<File> 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<File> 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<File> 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<ExecUtils>(), |
| CheckCompilationSpace) {} |
| |
| OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config, |
| 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) { |
| // 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<std::string> 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<time_t>(0), |
| kMaximumExecutionSeconds - GetExecutionTimeUsed()); |
| } |
| |
| time_t OnDeviceRefresh::GetSubprocessTimeout() const { |
| return std::min(GetExecutionTimeRemaining(), kMaxChildProcessSeconds); |
| } |
| |
| std::optional<std::vector<apex::ApexInfo>> OnDeviceRefresh::GetApexInfoList() const { |
| std::optional<apex::ApexInfoList> 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<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"); |
| |
| std::vector<apex::ApexInfo> 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<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(); |
| } |
| |
| 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 (const auto& [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_)); |
| } |
| |
| art_apex::ModuleInfo art_module_info = GenerateModuleInfo(art_apex_info.value()); |
| std::vector<art_apex::ModuleInfo> module_info_list = |
| GenerateModuleInfoList(apex_info_list.value()); |
| |
| std::vector<art_apex::Component> bcp_components = GenerateBootClasspathComponents(); |
| std::vector<art_apex::Component> dex2oat_bcp_components = |
| GenerateDex2oatBootClasspathComponents(); |
| std::vector<art_apex::SystemServerComponent> 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<art_apex::CacheInfo> 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<art_apex::Component> OnDeviceRefresh::GenerateBootClasspathComponents() const { |
| return GenerateComponents(boot_classpath_jars_); |
| } |
| |
| std::vector<art_apex::Component> OnDeviceRefresh::GenerateDex2oatBootClasspathComponents() const { |
| return GenerateComponents(dex2oat_boot_classpath_jars_); |
| } |
| |
| std::vector<art_apex::SystemServerComponent> OnDeviceRefresh::GenerateSystemServerComponents() |
| const { |
| return GenerateComponents<art_apex::SystemServerComponent>( |
| 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<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 (StartsWith(jar, 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 (!StartsWith(jar, 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::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/<isa>/boot.art". |
| return GetSystemImageFilename(GetPrimaryBootImage(on_system, minimal).c_str(), isa); |
| } |
| |
| std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtension() const { |
| std::vector<std::string> 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/<isa>/boot-framework.art". |
| return GetSystemImageFilename(GetSystemBootImageFrameworkExtension().c_str(), isa); |
| } |
| |
| std::string OnDeviceRefresh::GetBootImageMainlineExtension(bool on_system) const { |
| std::vector<std::string> 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/<isa>/boot-framework-adservices.art". |
| return GetSystemImageFilename(GetBootImageMainlineExtension(on_system).c_str(), isa); |
| } |
| |
| 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; |
| } |
| |
| 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/<isa>/services.art". |
| return ART_FORMAT("{}/oat/{}/{}", Dirname(jar_path), isa_str, image_name); |
| } else { |
| // Typically |
| // "/data/misc/apexdata/.../dalvik-cache/<isa>/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<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)) { |
| 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<std::string>* 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<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. |
| 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), |
| [](const char* prefix) { return StartsWith(prefix, "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<std::string, std::string> cached_system_properties; |
| std::unordered_set<std::string> 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_least_t = IsAtLeastT(); |
| bool kernel_supports_uffd = KernelSupportsUffd(); |
| if (!art::odrefresh::CheckBuildUserfaultFdGc( |
| build_enable_uffd_gc, is_at_least_t, kernel_supports_uffd)) { |
| // Assuming the system property reflects how the dexpreopted boot image was |
| // compiled, and it doesn't agree with runtime support, we need to recompile |
| // it. This happens if we're running on S, T or U, or if the system image |
| // was built with a wrong PRODUCT_ENABLE_UFFD_GC flag. |
| LOG(INFO) << ART_FORMAT( |
| "Userfaultfd GC check failed (build_enable_uffd_gc: {}, is_at_least_t: {}, " |
| "kernel_supports_uffd: {}).", |
| build_enable_uffd_gc, |
| is_at_least_t, |
| kernel_supports_uffd); |
| return false; |
| } |
| return true; |
| } |
| |
| WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForSystem( |
| const std::vector<apex::ApexInfo>& apex_info_list) const { |
| if (!CheckSystemPropertiesAreDefault()) { |
| return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); |
| } |
| |
| 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 (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<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(); |
| |
| 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<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); |
| } |
| |
| std::unordered_map<std::string, const art_apex::ModuleInfo*> 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<art_apex::Component> 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<art_apex::SystemServerComponent> 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<std::string>* 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<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(); |
| } |
| |
| std::set<std::string> 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<void> OnDeviceRefresh::CleanupArtifactDirectory( |
| OdrMetrics& metrics, const std::vector<std::string>& artifacts_to_keep) const { |
| const std::string& artifact_dir = config_.GetArtifactDirectory(); |
| std::unordered_set<std::string> 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<std::filesystem::directory_entry> 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<void> OnDeviceRefresh::RefreshExistingArtifacts() const { |
| const std::string& artifact_dir = config_.GetArtifactDirectory(); |
| if (!OS::DirectoryExists(artifact_dir.c_str())) { |
| return {}; |
| } |
| |
| std::vector<std::filesystem::directory_entry> 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<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()); |
| |
| InstructionSet system_server_isa = config_.GetSystemServerIsa(); |
| std::vector<std::string> 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<void> 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<std::string>& dex_files, |
| const std::vector<std::string>& boot_classpath, |
| const std::vector<std::string>& input_boot_images, |
| const OdrArtifacts& artifacts, |
| CmdlineBuilder&& extra_args, |
| /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii) const { |
| CmdlineBuilder args; |
| args.Add(config_.GetDex2Oat()); |
| |
| AddDex2OatCommonOptions(args); |
| 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()); |
| } |
| } |
| |
| args.Add("--oat-location=%s", artifacts.OatPath()); |
| std::pair<std::string, const char*> location_kind_pairs[] = { |
| std::make_pair(artifacts.ImagePath(), artifacts.ImageKind()), |
| std::make_pair(artifacts.OatPath(), "oat"), |
| std::make_pair(artifacts.VdexPath(), "output-vdex")}; |
| std::vector<std::unique_ptr<File>> staging_files; |
| for (const auto& [location, kind] : location_kind_pairs) { |
| std::string staging_location = GetStagingLocation(staging_dir, location); |
| std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str())); |
| if (staging_file == nullptr) { |
| return CompilationResult::Error( |
| OdrMetrics::Status::kIoError, |
| ART_FORMAT("Failed to create {} file '{}': {}", kind, staging_location, strerror(errno))); |
| } |
| // Don't check the state of the staging file. It doesn't need to be flushed because it's removed |
| // after the compilation regardless of success or failure. |
| staging_file->MarkUnchecked(); |
| args.Add(StringPrintf("--%s-fd=%d", kind, staging_file->Fd())); |
| staging_files.emplace_back(std::move(staging_file)); |
| } |
| |
| 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)); |
| } |
| |
| 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 (!MoveOrEraseFiles(staging_files, install_location)) { |
| return CompilationResult::Error( |
| OdrMetrics::Status::kIoError, |
| ART_FORMAT("Failed to commit artifacts to '{}'", install_location)); |
| } |
| |
| 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<std::string>& dex_files, |
| const std::vector<std::string>& boot_classpath, |
| const std::vector<std::string>& input_boot_images, |
| const std::string& output_path) const { |
| CmdlineBuilder args; |
| std::vector<std::unique_ptr<File>> 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<bool> 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> 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<void()>& 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<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; |
| } |
| |
| return result; |
| } |
| |
| WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oatForSystemServer( |
| const std::string& staging_dir, |
| const std::string& dex_file, |
| const std::vector<std::string>& classloader_context) const { |
| CmdlineBuilder args; |
| std::vector<std::unique_ptr<File>> 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<bool> 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<int> fds; |
| for (const std::string& path : classloader_context) { |
| std::string actual_path = RewriteParentDirectoryIfNeeded(path); |
| 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 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<std::string>& system_server_jars_to_compile, |
| const std::function<void()>& on_dex2oat_success) const { |
| DCHECK(!system_server_jars_to_compile.empty()); |
| |
| CompilationResult result = CompilationResult::Ok(); |
| std::vector<std::string> 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 { |
| const char* staging_dir = nullptr; |
| 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<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_.GetStagingDir().empty()) { |
| staging_dir = config_.GetStagingDir().c_str(); |
| } else { |
| // Create staging area and assign label for generating compilation artifacts. |
| if (PaletteCreateOdrefreshStagingDirectory(&staging_dir) != PALETTE_STATUS_OK) { |
| metrics.SetStatus(OdrMetrics::Status::kStagingFailed); |
| return ExitCode::kCleanupFailed; |
| } |
| } |
| |
| std::string error_msg; |
| |
| 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<InstructionSet>& 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<std::pair<OdrMetrics::Stage, OdrMetrics::Status>> 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()) { |
| 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 |