diff options
author | 2022-08-10 15:35:28 +0100 | |
---|---|---|
committer | 2022-08-31 16:55:25 +0000 | |
commit | 3aaecf0e678a241a25ac358dbf280a35a5c48efc (patch) | |
tree | 1e85d793126b9863c9428511620967a46d46b932 | |
parent | 0332ab40ba7e71dbf0a759a96017a96991d0ac19 (diff) |
ART services: optimize package - Add artd methods.
This change adds two artd methods: `getDexoptNeeded` and `dexopt`, which
are used for app compilation.
Also, this CL replaces all `StringPrintf` with `_format`.
Bug: 229268202
Test: m test-art-host-gtest-art_artd_tests
Ignore-AOSP-First: ART Services.
Change-Id: I51a42816750ff39c768658f739c7e6337cfe3e1c
-rw-r--r-- | artd/Android.bp | 6 | ||||
-rw-r--r-- | artd/art_standalone_artd_tests.xml | 51 | ||||
-rw-r--r-- | artd/artd.cc | 427 | ||||
-rw-r--r-- | artd/artd.h | 50 | ||||
-rw-r--r-- | artd/artd_test.cc | 626 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/DexMetadataPath.aidl | 29 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/DexoptOptions.aidl | 42 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/DexoptTrigger.aidl | 33 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/FsPermission.aidl | 2 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/GetDexoptNeededResult.aidl | 42 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/IArtd.aidl | 25 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/OutputArtifacts.aidl | 56 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/ProfilePath.aidl | 25 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/VdexPath.aidl | 29 | ||||
-rw-r--r-- | artd/file_utils.cc | 43 | ||||
-rw-r--r-- | artd/file_utils.h | 11 | ||||
-rw-r--r-- | artd/file_utils_test.cc | 23 | ||||
-rw-r--r-- | artd/path_utils.cc | 40 | ||||
-rw-r--r-- | artd/path_utils.h | 22 | ||||
-rw-r--r-- | artd/path_utils_test.cc | 32 |
20 files changed, 1525 insertions, 89 deletions
diff --git a/artd/Android.bp b/artd/Android.bp index 58680a5388..2170289069 100644 --- a/artd/Android.bp +++ b/artd/Android.bp @@ -34,6 +34,7 @@ cc_defaults { "libarttools", "libbase", "libbinder_ndk", + "libselinux", ], static_libs: [ "artd-aidl-ndk", @@ -75,6 +76,10 @@ art_cc_defaults { "file_utils_test.cc", "path_utils_test.cc", ], + data: [ + ":art-gtest-jars-Main", + ":art-gtest-jars-Nested", + ], } // Version of ART gtest `art_artd_tests` bundled with the ART APEX on target. @@ -99,4 +104,5 @@ art_cc_test { "art_standalone_gtest_defaults", "art_artd_tests_defaults", ], + test_config_template: "art_standalone_artd_tests.xml", } diff --git a/artd/art_standalone_artd_tests.xml b/artd/art_standalone_artd_tests.xml new file mode 100644 index 0000000000..9125046d58 --- /dev/null +++ b/artd/art_standalone_artd_tests.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> +<!-- Note: This test config file for {MODULE} is generated from a template. --> +<configuration description="Runs {MODULE}."> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" /> + <option name="append-bitness" value="true" /> + </target_preparer> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Main.jar" /> + <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Nested.jar" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" /> + <option name="module-name" value="{MODULE}" /> + <option name="ld-library-path-32" value="/apex/com.android.art/lib" /> + <option name="ld-library-path-64" value="/apex/com.android.art/lib64" /> + </test> + + <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if + one of the Mainline modules below is present on the device used for testing. --> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <!-- ART Mainline Module (internal version). --> + <option name="mainline-module-package-name" value="com.google.android.art" /> + <!-- ART Mainline Module (external (AOSP) version). --> + <option name="mainline-module-package-name" value="com.android.art" /> + </object> + + <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. --> + <!-- TODO(jiakaiz): Change this to U once `ro.build.version.sdk` is bumped. --> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" /> +</configuration> diff --git a/artd/artd.cc b/artd/artd.cc index fc99b2ae64..2cdca3c758 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -17,29 +17,47 @@ #include "artd.h" #include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> #include <unistd.h> +#include <climits> #include <cstdint> #include <filesystem> +#include <functional> +#include <map> #include <memory> +#include <optional> +#include <ostream> #include <string> +#include <string_view> #include <system_error> +#include <type_traits> #include <utility> #include <vector> #include "aidl/com/android/server/art/BnArtd.h" +#include "aidl/com/android/server/art/DexoptTrigger.h" #include "android-base/errors.h" +#include "android-base/file.h" #include "android-base/logging.h" #include "android-base/result.h" -#include "android-base/stringprintf.h" +#include "android-base/scopeguard.h" #include "android-base/strings.h" #include "android/binder_auto_utils.h" #include "android/binder_manager.h" #include "android/binder_process.h" +#include "base/compiler_filter.h" #include "base/file_utils.h" +#include "base/globals.h" +#include "base/os.h" +#include "file_utils.h" +#include "fmt/format.h" #include "oat_file_assistant.h" #include "oat_file_assistant_context.h" #include "path_utils.h" +#include "selinux/android.h" +#include "tools/cmdline_builder.h" #include "tools/tools.h" namespace art { @@ -48,16 +66,37 @@ namespace artd { namespace { using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexoptOptions; +using ::aidl::com::android::server::art::DexoptTrigger; +using ::aidl::com::android::server::art::FsPermission; +using ::aidl::com::android::server::art::GetDexoptNeededResult; using ::aidl::com::android::server::art::GetOptimizationStatusResult; +using ::aidl::com::android::server::art::OutputArtifacts; +using ::aidl::com::android::server::art::PriorityClass; +using ::aidl::com::android::server::art::ProfilePath; +using ::aidl::com::android::server::art::VdexPath; +using ::android::base::Dirname; using ::android::base::Error; +using ::android::base::Join; +using ::android::base::make_scope_guard; using ::android::base::Result; using ::android::base::Split; -using ::android::base::StringPrintf; using ::android::base::StringReplace; +using ::art::tools::CmdlineBuilder; using ::ndk::ScopedAStatus; +using ::fmt::literals::operator""_format; // NOLINT + +using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation; + constexpr const char* kServiceName = "artd"; +// Timeout for long operations, such as compilation. We set it to be smaller than the Package +// Manager watchdog (PackageManagerService.WATCHDOG_TIMEOUT, 10 minutes), so that if the operation +// is called from the Package Manager's thread handler, it will be aborted before that watchdog +// would take down the system server. +constexpr int kLongTimeoutSec = 570; // 9.5 minutes. + // Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an // error occurs. int64_t GetSizeAndDeleteFile(const std::string& path) { @@ -66,14 +105,13 @@ int64_t GetSizeAndDeleteFile(const std::string& path) { if (ec) { // It is okay if the file does not exist. We don't have to log it. if (ec.value() != ENOENT) { - LOG(ERROR) << StringPrintf( - "Failed to get the file size of '%s': %s", path.c_str(), ec.message().c_str()); + LOG(ERROR) << "Failed to get the file size of '{}': {}"_format(path, ec.message()); } return 0; } if (!std::filesystem::remove(path, ec)) { - LOG(ERROR) << StringPrintf("Failed to remove '%s': %s", path.c_str(), ec.message().c_str()); + LOG(ERROR) << "Failed to remove '{}': {}"_format(path, ec.message()); return 0; } @@ -103,6 +141,123 @@ ScopedAStatus NonFatal(const std::string& message) { EscapeErrorMessage(message).c_str()); } +Result<CompilerFilter::Filter> ParseCompilerFilter(const std::string& compiler_filter_str) { + CompilerFilter::Filter compiler_filter; + if (!CompilerFilter::ParseCompilerFilter(compiler_filter_str.c_str(), &compiler_filter)) { + return Errorf("Failed to parse compiler filter '{}'", compiler_filter_str); + } + return compiler_filter; +} + +OatFileAssistant::DexOptTrigger DexOptTriggerFromAidl(int8_t aidl_value) { + OatFileAssistant::DexOptTrigger trigger{}; + if ((aidl_value & static_cast<int8_t>(DexoptTrigger::COMPILER_FILTER_IS_BETTER)) != 0) { + trigger.targetFilterIsBetter = true; + } + if ((aidl_value & static_cast<int8_t>(DexoptTrigger::COMPILER_FILTER_IS_SAME)) != 0) { + trigger.targetFilterIsSame = true; + } + if ((aidl_value & static_cast<int8_t>(DexoptTrigger::COMPILER_FILTER_IS_WORSE)) != 0) { + trigger.targetFilterIsWorse = true; + } + if ((aidl_value & static_cast<int8_t>(DexoptTrigger::PRIMARY_BOOT_IMAGE_BECOMES_USABLE)) != 0) { + trigger.primaryBootImageBecomesUsable = true; + } + return trigger; +} + +ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) { + switch (location) { + case OatFileAssistant::Location::kLocationNoneOrError: + return ArtifactsLocation::NONE_OR_ERROR; + case OatFileAssistant::Location::kLocationOat: + return ArtifactsLocation::DALVIK_CACHE; + case OatFileAssistant::Location::kLocationOdex: + return ArtifactsLocation::NEXT_TO_DEX; + case OatFileAssistant::Location::kLocationDm: + return ArtifactsLocation::DM; + // No default. All cases should be explicitly handled, or the compilation will fail. + } + // This should never happen. Just in case we get a non-enumerator value. + LOG(FATAL) << "Unexpected Location " << location; +} + +Result<void> PrepareArtifactsDir( + const std::string& path, + const FsPermission& fs_permission, + const std::optional<OutputArtifacts::PermissionSettings::SeContext>& se_context = + std::nullopt) { + std::error_code ec; + bool created = std::filesystem::create_directory(path, ec); + if (ec) { + return Errorf("Failed to create directory '{}': {}", path, ec.message()); + } + + auto cleanup = make_scope_guard([&] { + if (created) { + std::filesystem::remove(path, ec); + } + }); + + if (chmod(path.c_str(), DirFsPermissionToMode(fs_permission)) != 0) { + return ErrnoErrorf("Failed to chmod directory '{}'", path); + } + OR_RETURN(Chown(path, fs_permission)); + + if (kIsTargetAndroid) { + int res = 0; + if (se_context.has_value()) { + res = selinux_android_restorecon_pkgdir(path.c_str(), + se_context->seInfo.c_str(), + se_context->packageUid, + SELINUX_ANDROID_RESTORECON_RECURSE); + } else { + res = selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE); + } + if (res != 0) { + return ErrnoErrorf("Failed to restorecon directory '{}'", path); + } + } + + cleanup.Disable(); + return {}; +} + +Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts) { + if (output_artifacts.artifactsPath.isInDalvikCache) { + return {}; + } + + std::filesystem::path oat_path(OR_RETURN(BuildOatPath(output_artifacts.artifactsPath))); + std::filesystem::path isa_dir = oat_path.parent_path(); + std::filesystem::path oat_dir = isa_dir.parent_path(); + DCHECK_EQ(oat_dir.filename(), "oat"); + + OR_RETURN(PrepareArtifactsDir(oat_dir, + output_artifacts.permissionSettings.dirFsPermission, + output_artifacts.permissionSettings.seContext)); + OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission)); + return {}; +} + +class FdLogger { + public: + void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); } + void Add(const File& file) { fd_mapping_.emplace_back(file.Fd(), file.GetPath()); } + + private: + std::vector<std::pair<int, std::string>> fd_mapping_; + + friend std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger); +}; + +std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger) { + for (const auto& [fd, path] : fd_logger.fd_mapping_) { + os << fd << ":" << path << ' '; + } + return os; +} + } // namespace #define OR_RETURN_ERROR(func, expr) \ @@ -172,6 +327,173 @@ ScopedAStatus Artd::getOptimizationStatus(const std::string& in_dexFile, return ScopedAStatus::ok(); } +ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::string& in_classLoaderContext, + const std::string& in_compilerFilter, + int8_t in_dexoptTrigger, + GetDexoptNeededResult* _aidl_return) { + Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext(); + if (!ofa_context.ok()) { + return NonFatal("Failed to get runtime options: " + ofa_context.error().message()); + } + + std::unique_ptr<ClassLoaderContext> context; + std::string error_msg; + auto oat_file_assistant = OatFileAssistant::Create(in_dexFile.c_str(), + in_instructionSet.c_str(), + in_classLoaderContext.c_str(), + /*load_executable=*/false, + /*only_load_trusted_executable=*/true, + ofa_context.value(), + &context, + &error_msg); + if (oat_file_assistant == nullptr) { + return NonFatal("Failed to create OatFileAssistant: " + error_msg); + } + + OatFileAssistant::DexOptStatus status; + _aidl_return->isDexoptNeeded = + oat_file_assistant->GetDexOptNeeded(OR_RETURN_FATAL(ParseCompilerFilter(in_compilerFilter)), + DexOptTriggerFromAidl(in_dexoptTrigger), + &status); + _aidl_return->isVdexUsable = status.IsVdexUsable(); + _aidl_return->artifactsLocation = ArtifactsLocationToAidl(status.GetLocation()); + + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::dexopt(const OutputArtifacts& in_outputArtifacts, + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::string& in_classLoaderContext, + const std::string& in_compilerFilter, + const std::optional<ProfilePath>& in_profile, + const std::optional<VdexPath>& in_inputVdex, + PriorityClass in_priorityClass, + const DexoptOptions& in_dexoptOptions, + bool* _aidl_return) { + std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_outputArtifacts.artifactsPath)); + std::string vdex_path = OatPathToVdexPath(oat_path); + std::string art_path = OatPathToArtPath(oat_path); + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + if (in_profile.has_value()) { + return Fatal("Profile-guided compilation not implemented"); + } + std::optional<std::string> profile_path = std::nullopt; + + std::unique_ptr<ClassLoaderContext> context = + ClassLoaderContext::Create(in_classLoaderContext.c_str()); + if (context == nullptr) { + return Fatal("Class loader context '{}' is invalid"_format(in_classLoaderContext)); + } + + OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts)); + + CmdlineBuilder args; + args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities"); + + if (in_priorityClass < PriorityClass::BOOT) { + args.Add("--set-task-profile=Dex2OatBootComplete").Add("--set-priority=background"); + } + + args.Add("--").Add(OR_RETURN_FATAL(GetDex2Oat())); + FdLogger fd_logger; + + const FsPermission& fs_permission = in_outputArtifacts.permissionSettings.fileFsPermission; + std::unique_ptr<NewFile> oat_file = OR_RETURN_NON_FATAL(NewFile::Create(oat_path, fs_permission)); + args.Add("--oat-fd=%d", oat_file->Fd()).Add("--oat-location=%s", oat_path); + fd_logger.Add(*oat_file); + + std::unique_ptr<NewFile> vdex_file = + OR_RETURN_NON_FATAL(NewFile::Create(vdex_path, fs_permission)); + args.Add("--output-vdex-fd=%d", vdex_file->Fd()); + fd_logger.Add(*vdex_file); + + std::vector<NewFile*> files_to_commit{oat_file.get(), vdex_file.get()}; + std::vector<std::string_view> files_to_delete; + + std::unique_ptr<NewFile> art_file = nullptr; + if (in_dexoptOptions.generateAppImage) { + art_file = OR_RETURN_NON_FATAL(NewFile::Create(art_path, fs_permission)); + args.Add("--app-image-fd=%d", art_file->Fd()); + fd_logger.Add(*art_file); + files_to_commit.push_back(art_file.get()); + } else { + files_to_delete.push_back(art_path); + } + + std::unique_ptr<NewFile> swap_file = nullptr; + if (ShouldCreateSwapFileForDexopt()) { + swap_file = OR_RETURN_NON_FATAL( + NewFile::Create("{}.swap"_format(oat_path), FsPermission{.uid = -1, .gid = -1})); + args.Add("--swap-fd=%d", swap_file->Fd()); + fd_logger.Add(*swap_file); + } + + std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); + args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile); + fd_logger.Add(*dex_file); + + std::vector<std::string> flattened_context = context->FlattenDexPaths(); + std::string dex_dir = Dirname(in_dexFile.c_str()); + std::vector<std::unique_ptr<File>> context_files; + std::vector<int> context_fds; + for (const std::string& context_element : flattened_context) { + std::string context_path = std::filesystem::path(dex_dir).append(context_element); + OR_RETURN_FATAL(ValidateDexPath(context_path)); + std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path)); + context_fds.push_back(context_file->Fd()); + fd_logger.Add(*context_file); + context_files.push_back(std::move(context_file)); + } + args.Add("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':')) + .Add("--class-loader-context=%s", in_classLoaderContext) + .Add("--classpath-dir=%s", dex_dir); + + std::unique_ptr<File> input_vdex_file = nullptr; + if (in_inputVdex.has_value()) { + if (in_inputVdex->getTag() == VdexPath::dexMetadataPath) { + std::string input_vdex_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_inputVdex.value())); + input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path)); + args.Add("--dm-fd=%d", input_vdex_file->Fd()); + } else { + std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value())); + input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path)); + args.Add("--input-vdex-fd=%d", input_vdex_file->Fd()); + } + fd_logger.Add(*input_vdex_file); + } + + std::unique_ptr<File> profile_file = nullptr; + if (profile_path.has_value()) { + profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value())); + args.Add("--profile-file-fd=%d", profile_file->Fd()); + fd_logger.Add(*profile_file); + } + + AddCompilerConfigFlags( + in_instructionSet, in_compilerFilter, in_priorityClass, in_dexoptOptions, args); + AddPerfConfigFlags(in_priorityClass, args); + + LOG(INFO) << "Running dex2oat: " << Join(args.Get(), /*separator=*/" ") + << "\nOpened FDs: " << fd_logger; + + Result<int> result = ExecAndReturnCode(args.Get(), kLongTimeoutSec); + if (!result.ok()) { + // TODO(b/244412198): Return false if dexopt is cancelled upon request. + return NonFatal("Failed to run dex2oat: " + result.error().message()); + } + if (result.value() != 0) { + return NonFatal("dex2oat returned an unexpected code: %d"_format(result.value())); + } + + NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete); + + *_aidl_return = true; + return ScopedAStatus::ok(); +} + Result<void> Artd::Start() { ScopedAStatus status = ScopedAStatus::fromStatus( AServiceManager_registerLazyService(this->asBinder().get(), kServiceName)); @@ -254,5 +576,100 @@ bool Artd::DenyArtApexDataFiles() { return cached_deny_art_apex_data_files_.value(); } +Result<std::string> Artd::GetArtExec() { return BuildArtBinPath("art_exec"); } + +bool Artd::ShouldUseDex2Oat64() { + return !props_->GetOrEmpty("ro.product.cpu.abilist64").empty() && + props_->GetBool("dalvik.vm.dex2oat64.enabled", /*default_value=*/false); +} + +Result<std::string> Artd::GetDex2Oat() { + std::string binary_name = ShouldUseDex2Oat64() ? "dex2oat64" : "dex2oat32"; + // TODO(b/234351700): Should we use the "d" variant? + return BuildArtBinPath(binary_name); +} + +bool Artd::ShouldCreateSwapFileForDexopt() { + // Create a swap file by default. Dex2oat will decide whether to use it or not. + return props_->GetBool("dalvik.vm.dex2oat-swap", /*default_value=*/true); +} + +void Artd::AddCompilerConfigFlags(const std::string& instruction_set, + const std::string& compiler_filter, + PriorityClass priority_class, + const DexoptOptions& dexopt_options, + /*out*/ CmdlineBuilder& args) { + args.Add("--instruction-set=%s", instruction_set); + std::string features_prop = "dalvik.vm.isa.{}.features"_format(instruction_set); + args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop)); + std::string variant_prop = "dalvik.vm.isa.{}.variant"_format(instruction_set); + args.AddIfNonEmpty("--instruction-set-variant=%s", props_->GetOrEmpty(variant_prop)); + + args.Add("--compiler-filter=%s", compiler_filter) + .Add("--compilation-reason=%s", dexopt_options.compilationReason); + + args.AddIf(priority_class >= PriorityClass::INTERACTIVE, "--compact-dex-level=none"); + + args.AddIfNonEmpty("--max-image-block-size=%s", + props_->GetOrEmpty("dalvik.vm.dex2oat-max-image-block-size")) + .AddIfNonEmpty("--very-large-app-threshold=%s", + props_->GetOrEmpty("dalvik.vm.dex2oat-very-large")) + .AddIfNonEmpty( + "--resolve-startup-const-strings=%s", + props_->GetOrEmpty("persist.device_config.runtime.dex2oat_resolve_startup_strings", + "dalvik.vm.dex2oat-resolve-startup-strings")); + + args.AddIf(dexopt_options.debuggable, "--debuggable") + .AddIf(props_->GetBool("debug.generate-debug-info", /*default_value=*/false), + "--generate-debug-info") + .AddIf(props_->GetBool("dalvik.vm.dex2oat-minidebuginfo", /*default_value=*/false), + "--generate-mini-debug-info"); + + args.AddRuntimeIf(DenyArtApexDataFiles(), "-Xdeny-art-apex-data-files") + .AddRuntime("-Xtarget-sdk-version:%d", dexopt_options.targetSdkVersion) + .AddRuntimeIf(dexopt_options.hiddenApiPolicyEnabled, "-Xhidden-api-policy:enabled"); +} + +void Artd::AddPerfConfigFlags(PriorityClass priority_class, /*out*/ CmdlineBuilder& args) { + // CPU set and number of threads. + std::string default_cpu_set_prop = "dalvik.vm.dex2oat-cpu-set"; + std::string default_threads_prop = "dalvik.vm.dex2oat-threads"; + std::string cpu_set; + std::string threads; + if (priority_class >= PriorityClass::BOOT) { + cpu_set = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-cpu-set"); + threads = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-threads"); + } else if (priority_class >= PriorityClass::INTERACTIVE_FAST) { + cpu_set = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-cpu-set", default_cpu_set_prop); + threads = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-threads", default_threads_prop); + } else if (priority_class <= PriorityClass::BACKGROUND) { + cpu_set = props_->GetOrEmpty("dalvik.vm.background-dex2oat-cpu-set", default_cpu_set_prop); + threads = props_->GetOrEmpty("dalvik.vm.background-dex2oat-threads", default_threads_prop); + } else { + cpu_set = props_->GetOrEmpty(default_cpu_set_prop); + threads = props_->GetOrEmpty(default_threads_prop); + } + args.AddIfNonEmpty("--cpu-set=%s", cpu_set).AddIfNonEmpty("-j%s", threads); + + args.AddRuntimeIfNonEmpty("-Xms%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xms")) + .AddRuntimeIfNonEmpty("-Xmx%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xmx")); + + // Enable compiling dex files in isolation on low ram devices. + // It takes longer but reduces the memory footprint. + args.AddIf(props_->GetBool("ro.config.low_ram", /*default_value=*/false), + "--compile-individually"); +} + +android::base::Result<int> Artd::ExecAndReturnCode(const std::vector<std::string>& args, + int timeout_sec) const { + bool ignored_timed_out = false; // This information is encoded in the error message. + std::string error_msg; + int exit_code = exec_utils_->ExecAndReturnCode(args, timeout_sec, &ignored_timed_out, &error_msg); + if (exit_code < 0) { + return Error() << error_msg; + } + return exit_code; +} + } // namespace artd } // namespace art diff --git a/artd/artd.h b/artd/artd.h index 25a4c86947..54d6776626 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -17,14 +17,18 @@ #ifndef ART_ARTD_ARTD_H_ #define ART_ARTD_ARTD_H_ +#include <cstdint> #include <memory> #include <string> +#include <utility> #include <vector> #include "aidl/com/android/server/art/BnArtd.h" #include "android-base/result.h" #include "android/binder_auto_utils.h" +#include "exec_utils.h" #include "oat_file_assistant_context.h" +#include "tools/cmdline_builder.h" #include "tools/system_properties.h" namespace art { @@ -33,8 +37,9 @@ namespace artd { class Artd : public aidl::com::android::server::art::BnArtd { public: explicit Artd(std::unique_ptr<art::tools::SystemProperties> props = - std::make_unique<art::tools::SystemProperties>()) - : props_(std::move(props)) {} + std::make_unique<art::tools::SystemProperties>(), + std::unique_ptr<ExecUtils> exec_utils = std::make_unique<ExecUtils>()) + : props_(std::move(props)), exec_utils_(std::move(exec_utils)) {} ndk::ScopedAStatus isAlive(bool* _aidl_return) override; @@ -48,6 +53,26 @@ class Artd : public aidl::com::android::server::art::BnArtd { const std::string& in_classLoaderContext, aidl::com::android::server::art::GetOptimizationStatusResult* _aidl_return) override; + ndk::ScopedAStatus getDexoptNeeded( + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::string& in_classLoaderContext, + const std::string& in_compilerFilter, + int8_t in_dexoptTrigger, + aidl::com::android::server::art::GetDexoptNeededResult* _aidl_return) override; + + ndk::ScopedAStatus dexopt( + const aidl::com::android::server::art::OutputArtifacts& in_outputArtifacts, + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::string& in_classLoaderContext, + const std::string& in_compilerFilter, + const std::optional<aidl::com::android::server::art::ProfilePath>& in_profile, + const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex, + aidl::com::android::server::art::PriorityClass in_priorityClass, + const aidl::com::android::server::art::DexoptOptions& in_dexoptOptions, + bool* _aidl_return) override; + android::base::Result<void> Start(); private: @@ -61,6 +86,26 @@ class Artd : public aidl::com::android::server::art::BnArtd { bool DenyArtApexDataFiles(); + android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector, + int timeout_sec) const; + + android::base::Result<std::string> GetArtExec(); + + bool ShouldUseDex2Oat64(); + + android::base::Result<std::string> GetDex2Oat(); + + bool ShouldCreateSwapFileForDexopt(); + + void AddCompilerConfigFlags(const std::string& instruction_set, + const std::string& compiler_filter, + aidl::com::android::server::art::PriorityClass priority_class, + const aidl::com::android::server::art::DexoptOptions& dexopt_options, + /*out*/ art::tools::CmdlineBuilder& args); + + void AddPerfConfigFlags(aidl::com::android::server::art::PriorityClass priority_class, + /*out*/ art::tools::CmdlineBuilder& args); + std::optional<std::vector<std::string>> cached_boot_image_locations_; std::optional<std::vector<std::string>> cached_boot_class_path_; std::optional<std::string> cached_apex_versions_; @@ -70,6 +115,7 @@ class Artd : public aidl::com::android::server::art::BnArtd { std::unique_ptr<OatFileAssistantContext> ofa_context_; std::unique_ptr<art::tools::SystemProperties> props_; + std::unique_ptr<ExecUtils> exec_utils_; }; } // namespace artd diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 129e31cd01..8fd9e96a57 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -16,29 +16,67 @@ #include "artd.h" +#include <algorithm> #include <filesystem> #include <functional> #include <memory> +#include <optional> +#include <string> +#include <type_traits> +#include <vector> +#include "aidl/com/android/server/art/BnArtd.h" +#include "android-base/errors.h" #include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/parseint.h" +#include "android-base/result.h" #include "android-base/scopeguard.h" -#include "android/binder_interface_utils.h" +#include "android-base/strings.h" +#include "android/binder_auto_utils.h" +#include "android/binder_status.h" #include "base/common_art_test.h" +#include "exec_utils.h" +#include "fmt/format.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "path_utils.h" +#include "tools/system_properties.h" namespace art { namespace artd { namespace { using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::DexoptOptions; +using ::aidl::com::android::server::art::FsPermission; +using ::aidl::com::android::server::art::OutputArtifacts; +using ::aidl::com::android::server::art::PriorityClass; +using ::aidl::com::android::server::art::VdexPath; using ::android::base::make_scope_guard; +using ::android::base::ParseInt; +using ::android::base::ReadFileToString; +using ::android::base::Result; using ::android::base::ScopeGuard; +using ::android::base::Split; +using ::android::base::WriteStringToFd; +using ::android::base::WriteStringToFile; using ::testing::_; +using ::testing::AllOf; +using ::testing::AnyNumber; +using ::testing::Contains; using ::testing::ContainsRegex; +using ::testing::DoAll; +using ::testing::ElementsAre; using ::testing::HasSubstr; using ::testing::MockFunction; +using ::testing::Not; +using ::testing::ResultOf; +using ::testing::Return; +using ::testing::WithArg; + +using ::fmt::literals::operator""_format; // NOLINT ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) { android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger)); @@ -47,12 +85,143 @@ ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& l }); } +void CheckContent(const std::string& path, const std::string& expected_content) { + std::string actual_content; + ASSERT_TRUE(ReadFileToString(path, &actual_content)); + EXPECT_EQ(actual_content, expected_content); +} + +// Writes `content` to the FD specified by the `flag`. +ACTION_P(WriteToFdFlag, flag, content) { + for (const std::string& arg : arg0) { + std::string_view value(arg); + if (android::base::ConsumePrefix(&value, flag)) { + int fd; + ASSERT_TRUE(ParseInt(std::string(value), &fd)); + ASSERT_TRUE(WriteStringToFd(content, fd)); + return; + } + } + FAIL() << "Flag '{}' not found"_format(flag); +} + +// Matches a flag that starts with `flag` and whose value matches `matcher`. +MATCHER_P2(Flag, flag, matcher, "") { + std::string_view value(arg); + if (!android::base::ConsumePrefix(&value, flag)) { + return false; + } + return ExplainMatchResult(matcher, std::string(value), result_listener); +} + +// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches +// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument. +MATCHER_P2(ListFlag, flag, matcher, "") { + return ExplainMatchResult( + Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)), + arg, + result_listener); +} + +// Matches an FD of a file whose path matches `matcher`. +MATCHER_P(FdOf, matcher, "") { + int fd; + if (!ParseInt(arg, &fd)) { + return false; + } + std::string proc_path = "/proc/self/fd/{}"_format(fd); + char path[PATH_MAX]; + ssize_t len = readlink(proc_path.c_str(), path, sizeof(path)); + if (len < 0) { + return false; + } + return ExplainMatchResult(matcher, std::string(path, static_cast<size_t>(len)), result_listener); +} + +// Matches a container that, when split by `separator`, the first part matches `head_matcher`, and +// the second part matches `tail_matcher`. +MATCHER_P3(WhenSplitBy, separator, head_matcher, tail_matcher, "") { + using Value = const typename std::remove_reference<decltype(arg)>::type::value_type; + auto it = std::find(arg.begin(), arg.end(), separator); + if (it == arg.end()) { + return false; + } + size_t pos = it - arg.begin(); + return ExplainMatchResult(head_matcher, ArrayRef<Value>(arg).SubArray(0, pos), result_listener) && + ExplainMatchResult(tail_matcher, ArrayRef<Value>(arg).SubArray(pos + 1), result_listener); +} + +class MockSystemProperties : public tools::SystemProperties { + public: + MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override)); +}; + +class MockExecUtils : public ExecUtils { + public: + // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead + // to a conflict between gmock and android-base/logging.h (b/132668253). + int ExecAndReturnCode(const std::vector<std::string>& arg_vector, + int, + bool*, + std::string*) const override { + return DoExecAndReturnCode(arg_vector); + } + + MOCK_METHOD(int, DoExecAndReturnCode, (const std::vector<std::string>& arg_vector), (const)); +}; + class ArtdTest : public CommonArtTest { protected: void SetUp() override { CommonArtTest::SetUp(); - artd_ = ndk::SharedRefBase::make<Artd>(); + auto mock_props = std::make_unique<MockSystemProperties>(); + mock_props_ = mock_props.get(); + EXPECT_CALL(*mock_props_, GetProperty).Times(AnyNumber()).WillRepeatedly(Return("")); + auto mock_exec_utils = std::make_unique<MockExecUtils>(); + mock_exec_utils_ = mock_exec_utils.get(); + artd_ = ndk::SharedRefBase::make<Artd>(std::move(mock_props), std::move(mock_exec_utils)); scratch_dir_ = std::make_unique<ScratchDir>(); + scratch_path_ = scratch_dir_->GetPath(); + // Remove the trailing '/'; + scratch_path_.resize(scratch_path_.length() - 1); + + // Use an arbitrary existing directory as ART root. + art_root_ = scratch_path_ + "/com.android.art"; + std::filesystem::create_directories(art_root_); + setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1); + + dex_file_ = scratch_path_ + "/a/b.apk"; + isa_ = "arm64"; + artifacts_path_ = ArtifactsPath{ + .dexPath = dex_file_, + .isa = isa_, + .isInDalvikCache = false, + }; + struct stat st; + ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0); + output_artifacts_ = OutputArtifacts{ + .artifactsPath = artifacts_path_, + .permissionSettings = + OutputArtifacts::PermissionSettings{ + .dirFsPermission = + FsPermission{ + .uid = static_cast<int32_t>(st.st_uid), + .gid = static_cast<int32_t>(st.st_gid), + .isOtherReadable = true, + .isOtherExecutable = true, + }, + .fileFsPermission = + FsPermission{ + .uid = static_cast<int32_t>(st.st_uid), + .gid = static_cast<int32_t>(st.st_gid), + .isOtherReadable = true, + }, + }, + }; + clc_1_ = GetTestDexFileName("Main"); + clc_2_ = GetTestDexFileName("Nested"); + class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_); + compiler_filter_ = "speed"; } void TearDown() override { @@ -60,9 +229,63 @@ class ArtdTest : public CommonArtTest { CommonArtTest::TearDown(); } + void RunDexopt(binder_exception_t expected_status = EX_NONE, bool expected_aidl_return = true) { + InitDexoptInputFiles(); + bool aidl_return; + ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_, + dex_file_, + isa_, + class_loader_context_, + compiler_filter_, + /*in_profile=*/std::nullopt, + vdex_path_, + priority_class_, + dexopt_options_, + &aidl_return); + ASSERT_EQ(status.getExceptionCode(), expected_status) << status.getMessage(); + if (status.isOk()) { + ASSERT_EQ(aidl_return, expected_aidl_return); + } + } + std::shared_ptr<Artd> artd_; std::unique_ptr<ScratchDir> scratch_dir_; + std::string scratch_path_; + std::string art_root_; MockFunction<android::base::LogFunction> mock_logger_; + ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT"); + MockSystemProperties* mock_props_; + MockExecUtils* mock_exec_utils_; + + std::string dex_file_; + std::string isa_; + ArtifactsPath artifacts_path_; + OutputArtifacts output_artifacts_; + std::string clc_1_; + std::string clc_2_; + std::string class_loader_context_; + std::string compiler_filter_; + std::optional<VdexPath> vdex_path_; + PriorityClass priority_class_ = PriorityClass::BACKGROUND; + DexoptOptions dexopt_options_; + + private: + void CreateFile(const std::string& filename) { + std::filesystem::path path(filename); + std::filesystem::create_directories(path.parent_path()); + WriteStringToFile("", filename); + } + + void InitDexoptInputFiles() { + CreateFile(dex_file_); + if (vdex_path_.has_value()) { + if (vdex_path_->getTag() == VdexPath::dexMetadataPath) { + CreateFile(OR_FATAL(BuildDexMetadataPath(vdex_path_.value()))); + } else { + CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value()))); + } + } + } }; TEST_F(ArtdTest, isAlive) { @@ -72,22 +295,14 @@ TEST_F(ArtdTest, isAlive) { } TEST_F(ArtdTest, deleteArtifacts) { - std::string oat_dir = scratch_dir_->GetPath() + "/a/oat/arm64"; + std::string oat_dir = scratch_path_ + "/a/oat/arm64"; std::filesystem::create_directories(oat_dir); - android::base::WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes. - android::base::WriteStringToFile("ab", oat_dir + "/b.vdex"); // 2 bytes. - android::base::WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte. + WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes. + WriteStringToFile("ab", oat_dir + "/b.vdex"); // 2 bytes. + WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte. int64_t result = -1; - EXPECT_TRUE(artd_ - ->deleteArtifacts( - ArtifactsPath{ - .dexPath = scratch_dir_->GetPath() + "/a/b.apk", - .isa = "arm64", - .isInDalvikCache = false, - }, - &result) - .isOk()); + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); EXPECT_EQ(result, 4 + 2 + 1); EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex")); @@ -99,8 +314,8 @@ TEST_F(ArtdTest, deleteArtifactsMissingFile) { // Missing VDEX file. std::string oat_dir = dalvik_cache_ + "/arm64"; std::filesystem::create_directories(oat_dir); - android::base::WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex"); // 4 bytes. - android::base::WriteStringToFile("a", oat_dir + "/a@b.apk@classes.art"); // 1 byte. + WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex"); // 4 bytes. + WriteStringToFile("a", oat_dir + "/a@b.apk@classes.art"); // 1 byte. auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0); @@ -126,24 +341,16 @@ TEST_F(ArtdTest, deleteArtifactsNoFile) { EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0); int64_t result = -1; - EXPECT_TRUE(artd_ - ->deleteArtifacts( - ArtifactsPath{ - .dexPath = android_data_ + "/a/b.apk", - .isa = "arm64", - .isInDalvikCache = false, - }, - &result) - .isOk()); + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); EXPECT_EQ(result, 0); } TEST_F(ArtdTest, deleteArtifactsPermissionDenied) { - std::string oat_dir = scratch_dir_->GetPath() + "/a/oat/arm64"; + std::string oat_dir = scratch_path_ + "/a/oat/arm64"; std::filesystem::create_directories(oat_dir); - android::base::WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes. - android::base::WriteStringToFile("ab", oat_dir + "/b.vdex"); // 2 bytes. - android::base::WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte. + WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes. + WriteStringToFile("ab", oat_dir + "/b.vdex"); // 2 bytes. + WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte. auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(3); @@ -152,25 +359,17 @@ TEST_F(ArtdTest, deleteArtifactsPermissionDenied) { auto scoped_unroot = ScopedUnroot(); int64_t result = -1; - EXPECT_TRUE(artd_ - ->deleteArtifacts( - ArtifactsPath{ - .dexPath = scratch_dir_->GetPath() + "/a/b.apk", - .isa = "arm64", - .isInDalvikCache = false, - }, - &result) - .isOk()); + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); EXPECT_EQ(result, 0); } TEST_F(ArtdTest, deleteArtifactsFileIsDir) { // VDEX file is a directory. - std::string oat_dir = scratch_dir_->GetPath() + "/a/oat/arm64"; + std::string oat_dir = scratch_path_ + "/a/oat/arm64"; std::filesystem::create_directories(oat_dir); std::filesystem::create_directories(oat_dir + "/b.vdex"); - android::base::WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes. - android::base::WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte. + WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes. + WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte. auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); EXPECT_CALL(mock_logger_, @@ -178,15 +377,7 @@ TEST_F(ArtdTest, deleteArtifactsFileIsDir) { .Times(1); int64_t result = -1; - EXPECT_TRUE(artd_ - ->deleteArtifacts( - ArtifactsPath{ - .dexPath = scratch_dir_->GetPath() + "/a/b.apk", - .isa = "arm64", - .isInDalvikCache = false, - }, - &result) - .isOk()); + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); EXPECT_EQ(result, 4 + 1); // The directory is kept because getting the file size failed. @@ -195,6 +386,339 @@ TEST_F(ArtdTest, deleteArtifactsFileIsDir) { EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art")); } +TEST_F(ArtdTest, dexopt) { + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/dex2oat32"), + Contains(Flag("--zip-fd=", FdOf(dex_file_))), + Contains(Flag("--zip-location=", dex_file_)), + Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")), + Contains(Flag("--instruction-set=", "arm64")), + Contains(Flag("--compiler-filter=", "speed")))))) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")), + WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")), + Return(0))); + RunDexopt(); + + CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex"); +} + +TEST_F(ArtdTest, dexoptClassLoaderContext) { + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", + _, + AllOf(Contains(ListFlag("--class-loader-context-fds=", + ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))), + Contains(Flag("--class-loader-context=", class_loader_context_)), + Contains(Flag("--classpath-dir=", scratch_path_ + "/a")))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptNoInputVdex) { + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Not(Contains(Flag("--dm-fd=", _))), + Not(Contains(Flag("--input-vdex-fd=", _))))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptInputVdex) { + vdex_path_ = artifacts_path_; + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", + _, + AllOf(Not(Contains(Flag("--dm-fd=", _))), + Contains(Flag("--input-vdex-fd=", FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptInputVdexDm) { + vdex_path_ = DexMetadataPath{.dexPath = dex_file_}; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))), + Not(Contains(Flag("--input-vdex-fd=", _))))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassBoot) { + priority_class_ = PriorityClass::BOOT; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + AllOf(Not(Contains(Flag("--set-task-profile=", _))), + Not(Contains(Flag("--set-priority=", _)))), + Contains(Flag("--compact-dex-level=", "none"))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassInteractive) { + priority_class_ = PriorityClass::INTERACTIVE; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")), + Contains(Flag("--set-priority=", "background"))), + Contains(Flag("--compact-dex-level=", "none"))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassInteractiveFast) { + priority_class_ = PriorityClass::INTERACTIVE_FAST; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")), + Contains(Flag("--set-priority=", "background"))), + Contains(Flag("--compact-dex-level=", "none"))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassBackground) { + priority_class_ = PriorityClass::BACKGROUND; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")), + Contains(Flag("--set-priority=", "background"))), + Not(Contains(Flag("--compact-dex-level=", _)))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptDexoptOptions) { + dexopt_options_ = DexoptOptions{ + .compilationReason = "install", + .targetSdkVersion = 123, + .debuggable = false, + .generateAppImage = false, + .hiddenApiPolicyEnabled = false, + }; + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Contains(Flag("--compilation-reason=", "install")), + Contains(Flag("-Xtarget-sdk-version:", "123")), + Not(Contains("--debuggable")), + Not(Contains(Flag("--app-image-fd=", _))), + Not(Contains(Flag("-Xhidden-api-policy:", _))))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptDexoptOptions2) { + dexopt_options_ = DexoptOptions{ + .compilationReason = "bg-dexopt", + .targetSdkVersion = 456, + .debuggable = true, + .generateAppImage = true, + .hiddenApiPolicyEnabled = true, + }; + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Contains(Flag("--compilation-reason=", "bg-dexopt")), + Contains(Flag("-Xtarget-sdk-version:", "456")), + Contains("--debuggable"), + Contains(Flag("-Xhidden-api-policy:", "enabled")))))) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")), Return(0))); + RunDexopt(); + + CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art"); +} + +TEST_F(ArtdTest, dexoptDefaultFlagsWhenNoSystemProps) { + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains(Flag("--swap-fd=", FdOf(_))), + Not(Contains(Flag("--instruction-set-features=", _))), + Not(Contains(Flag("--instruction-set-variant=", _))), + Not(Contains(Flag("--max-image-block-size=", _))), + Not(Contains(Flag("--very-large-app-threshold=", _))), + Not(Contains(Flag("--resolve-startup-const-strings=", _))), + Not(Contains("--generate-debug-info")), + Not(Contains("--generate-mini-debug-info")), + Contains("-Xdeny-art-apex-data-files"), + Not(Contains(Flag("--cpu-set=", _))), + Not(Contains(Flag("-j", _))), + Not(Contains(Flag("-Xms", _))), + Not(Contains(Flag("-Xmx", _))), + Not(Contains("--compile-individually")))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptFlagsFromSystemProps) { + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-swap")).WillOnce(Return("0")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.features")) + .WillOnce(Return("features")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.variant")).WillOnce(Return("variant")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-max-image-block-size")) + .WillOnce(Return("size")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-very-large")) + .WillOnce(Return("threshold")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-resolve-startup-strings")) + .WillOnce(Return("strings")); + EXPECT_CALL(*mock_props_, GetProperty("debug.generate-debug-info")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-minidebuginfo")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("odsign.verification.success")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xms")).WillOnce(Return("xms")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xmx")).WillOnce(Return("xmx")); + EXPECT_CALL(*mock_props_, GetProperty("ro.config.low_ram")).WillOnce(Return("1")); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Not(Contains(Flag("--swap-fd=", _))), + Contains(Flag("--instruction-set-features=", "features")), + Contains(Flag("--instruction-set-variant=", "variant")), + Contains(Flag("--max-image-block-size=", "size")), + Contains(Flag("--very-large-app-threshold=", "threshold")), + Contains(Flag("--resolve-startup-const-strings=", "strings")), + Contains("--generate-debug-info"), + Contains("--generate-mini-debug-info"), + Not(Contains("-Xdeny-art-apex-data-files")), + Contains(Flag("-Xms", "xms")), + Contains(Flag("-Xmx", "xmx")), + Contains("--compile-individually"))))) + .WillOnce(Return(0)); + RunDexopt(); +} + +static void SetDefaultResourceControlProps(MockSystemProperties* mock_props) { + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4")); +} + +TEST_F(ArtdTest, dexoptDefaultResourceControlBoot) { + SetDefaultResourceControlProps(mock_props_); + + // The default resource control properties don't apply to BOOT. + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _))))))) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::BOOT; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptDefaultResourceControlOther) { + SetDefaultResourceControlProps(mock_props_); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))))) + .Times(3) + .WillRepeatedly(Return(0)); + priority_class_ = PriorityClass::INTERACTIVE_FAST; + RunDexopt(); + priority_class_ = PriorityClass::INTERACTIVE; + RunDexopt(); + priority_class_ = PriorityClass::BACKGROUND; + RunDexopt(); +} + +static void SetAllResourceControlProps(MockSystemProperties* mock_props) { + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-cpu-set")) + .WillRepeatedly(Return("0,1,2,3")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-threads")) + .WillRepeatedly(Return("8")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-cpu-set")) + .WillRepeatedly(Return("0,2,3")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-threads")) + .WillRepeatedly(Return("6")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-cpu-set")) + .WillRepeatedly(Return("0")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-threads")) + .WillRepeatedly(Return("2")); +} + +TEST_F(ArtdTest, dexoptAllResourceControlBoot) { + SetAllResourceControlProps(mock_props_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8")))))) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::BOOT; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptAllResourceControlInteractiveFast) { + SetAllResourceControlProps(mock_props_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6")))))) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::INTERACTIVE_FAST; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptAllResourceControlInteractive) { + SetAllResourceControlProps(mock_props_); + + // INTERACTIVE always uses the default resource control properties. + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))))) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::INTERACTIVE; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptAllResourceControlBackground) { + SetAllResourceControlProps(mock_props_); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2")))))) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::BACKGROUND; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptFailed) { + dexopt_options_.generateAppImage = true; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")), + WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")), + WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")), + Return(1))); + RunDexopt(EX_SERVICE_SPECIFIC); + + EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.odex")); + EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.vdex")); + EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art")); +} + } // namespace } // namespace artd } // namespace art diff --git a/artd/binder/com/android/server/art/DexMetadataPath.aidl b/artd/binder/com/android/server/art/DexMetadataPath.aidl new file mode 100644 index 0000000000..5f9ab81ece --- /dev/null +++ b/artd/binder/com/android/server/art/DexMetadataPath.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * Represents the path to a dex metadata file. + * + * @hide + */ +parcelable DexMetadataPath { + /** + * The absolute path starting with '/' to the dex file that the dex metadata file is next to. + */ + @utf8InCpp String dexPath; +} diff --git a/artd/binder/com/android/server/art/DexoptOptions.aidl b/artd/binder/com/android/server/art/DexoptOptions.aidl new file mode 100644 index 0000000000..351c079f40 --- /dev/null +++ b/artd/binder/com/android/server/art/DexoptOptions.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * Miscellaneous options for performing dexopt. Every field corresponds to a dex2oat command line + * flag. + * + * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes + * static flags, and flags that only depend on system properties or other passed parameters, such as + * the priority class. + * + * All fields are required. + * + * @hide + */ +parcelable DexoptOptions { + /** --compilation-reason */ + @utf8InCpp String compilationReason; + /** -Xtarget-sdk-version */ + int targetSdkVersion; + /** --debuggable */ + boolean debuggable; + /** --app-image-fd */ + boolean generateAppImage; + /** -Xhidden-api-policy:enabled */ + boolean hiddenApiPolicyEnabled; +} diff --git a/artd/binder/com/android/server/art/DexoptTrigger.aidl b/artd/binder/com/android/server/art/DexoptTrigger.aidl new file mode 100644 index 0000000000..a160f22fa6 --- /dev/null +++ b/artd/binder/com/android/server/art/DexoptTrigger.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * Represents the conditions where dexopt should be performed. + * See `OatFileAssistant::DexOptTrigger`. + * + * This is actually used as a bit field, but is declared as an enum because AIDL doesn't support bit + * fields. + * + * @hide + */ +enum DexoptTrigger { + COMPILER_FILTER_IS_BETTER = 1 << 0, + COMPILER_FILTER_IS_SAME = 1 << 1, + COMPILER_FILTER_IS_WORSE = 1 << 2, + PRIMARY_BOOT_IMAGE_BECOMES_USABLE = 1 << 3, +} diff --git a/artd/binder/com/android/server/art/FsPermission.aidl b/artd/binder/com/android/server/art/FsPermission.aidl index 229f1829bd..9c2ddb9b90 100644 --- a/artd/binder/com/android/server/art/FsPermission.aidl +++ b/artd/binder/com/android/server/art/FsPermission.aidl @@ -19,6 +19,8 @@ package com.android.server.art; /** * Represents the Linux filesystem permission of a file or a directory. * + * If both `uid` and `gid` are negative, no `chown` will be performed. + * * If none of the booleans are set, the default permission bits are `rw-r-----` for a file, and * `rwxr-x---` for a directory. * diff --git a/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl new file mode 100644 index 0000000000..99c4951d10 --- /dev/null +++ b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * The result of {@code IArtd.getDexoptNeeded}. + * + * @hide + */ +parcelable GetDexoptNeededResult { + /** Whether dexopt is needed. */ + boolean isDexoptNeeded; + /** Whether there is a usable VDEX file. Note that this can be true even if dexopt is needed. */ + boolean isVdexUsable; + /** The location of the best usable artifacts. */ + ArtifactsLocation artifactsLocation = ArtifactsLocation.NONE_OR_ERROR; + + enum ArtifactsLocation { + /** No usable artifacts. */ + NONE_OR_ERROR = 0, + /** In the global "dalvik-cache" folder. */ + DALVIK_CACHE = 1, + /** In the "oat" folder next to the dex file. */ + NEXT_TO_DEX = 2, + /** In the dex metadata file. This means the only usable artifact is the VDEX file. */ + DM = 3, + } +} diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 5cf23b9e9f..6750d9cd2e 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -36,4 +36,29 @@ interface IArtd { com.android.server.art.GetOptimizationStatusResult getOptimizationStatus( @utf8InCpp String dexFile, @utf8InCpp String instructionSet, @utf8InCpp String classLoaderContext); + + /** + * Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values + * defined in `com.android.server.art.DexoptTrigger`. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.GetDexoptNeededResult getDexoptNeeded( + @utf8InCpp String dexFile, @utf8InCpp String instructionSet, + @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter, + byte dexoptTrigger); + + /** + * Dexopts a dex file for the given instruction set. Returns true on success, or false if + * cancelled. + * + * Throws fatal and non-fatal errors. + */ + boolean dexopt(in com.android.server.art.OutputArtifacts outputArtifacts, + @utf8InCpp String dexFile, @utf8InCpp String instructionSet, + @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter, + in @nullable com.android.server.art.ProfilePath profile, + in @nullable com.android.server.art.VdexPath inputVdex, + com.android.server.art.PriorityClass priorityClass, + in com.android.server.art.DexoptOptions dexoptOptions); } diff --git a/artd/binder/com/android/server/art/OutputArtifacts.aidl b/artd/binder/com/android/server/art/OutputArtifacts.aidl new file mode 100644 index 0000000000..20ed4753d0 --- /dev/null +++ b/artd/binder/com/android/server/art/OutputArtifacts.aidl @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * Represents output optimized artifacts of a dex file (i.e., ART, OAT, and VDEX files). + * + * @hide + */ +parcelable OutputArtifacts { + /** The path to the output. */ + com.android.server.art.ArtifactsPath artifactsPath; + + parcelable PermissionSettings { + /** + * The permission of the directories that contain the artifacts. Has no effect if + * `artifactsPath.isInDalvikCache` is true. + */ + com.android.server.art.FsPermission dirFsPermission; + + /** The permission of the files. */ + com.android.server.art.FsPermission fileFsPermission; + + /** The tuple used for looking up for the SELinux context. */ + parcelable SeContext { + /** The seinfo tag in SELinux policy. */ + @utf8InCpp String seInfo; + + /** The package uid. */ + int packageUid; + } + + /** + * Determines the SELinux context of the directories and the files. If empty, the default + * context based on the file path will be used. Has no effect if + * `artifactsPath.isInDalvikCache` is true. + */ + @nullable SeContext seContext; + } + + PermissionSettings permissionSettings; +} diff --git a/artd/binder/com/android/server/art/ProfilePath.aidl b/artd/binder/com/android/server/art/ProfilePath.aidl new file mode 100644 index 0000000000..d9e12083f7 --- /dev/null +++ b/artd/binder/com/android/server/art/ProfilePath.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * Represents the path to a profile file. + * + * @hide + */ +parcelable ProfilePath { +} diff --git a/artd/binder/com/android/server/art/VdexPath.aidl b/artd/binder/com/android/server/art/VdexPath.aidl new file mode 100644 index 0000000000..ec23a0ea54 --- /dev/null +++ b/artd/binder/com/android/server/art/VdexPath.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.server.art; + +/** + * Represents the path to a VDEX file. + * + * @hide + */ +union VdexPath { + /** Represents a VDEX file as part of the artifacts. */ + com.android.server.art.ArtifactsPath artifactsPath; + /** Represents a VDEX file in a dex metadata file. */ + com.android.server.art.DexMetadataPath dexMetadataPath; +} diff --git a/artd/file_utils.cc b/artd/file_utils.cc index 67185b86f1..45344e1343 100644 --- a/artd/file_utils.cc +++ b/artd/file_utils.cc @@ -33,9 +33,9 @@ #include "android-base/logging.h" #include "android-base/result.h" #include "android-base/scopeguard.h" -#include "android-base/stringprintf.h" #include "base/os.h" #include "base/unix_file/fd_file.h" +#include "fmt/format.h" namespace art { namespace artd { @@ -45,14 +45,14 @@ namespace { using ::aidl::com::android::server::art::FsPermission; using ::android::base::make_scope_guard; using ::android::base::Result; -using ::android::base::StringPrintf; + +using ::fmt::literals::operator""_format; // NOLINT void UnlinkIfExists(const std::string& path) { std::error_code ec; if (!std::filesystem::remove(path, ec)) { if (ec.value() != ENOENT) { - LOG(WARNING) << StringPrintf( - "Failed to remove file '%s': %s", path.c_str(), ec.message().c_str()); + LOG(WARNING) << "Failed to remove file '{}': {}"_format(path, ec.message()); } } } @@ -100,7 +100,7 @@ void NewFile::Cleanup() { } Result<void> NewFile::Init() { - mode_t mode = FsPermissionToMode(fs_permission_); + mode_t mode = FileFsPermissionToMode(fs_permission_); // "<path_>.XXXXXX.tmp". temp_path_ = BuildTempPath(final_path_, "XXXXXX"); fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4); @@ -111,9 +111,7 @@ Result<void> NewFile::Init() { if (fchmod(fd_, mode) != 0) { return ErrnoErrorf("Failed to chmod file '{}'", temp_path_); } - if (fchown(fd_, fs_permission_.uid, fs_permission_.gid) != 0) { - return ErrnoErrorf("Failed to chown file '{}'", temp_path_); - } + OR_RETURN(Chown(temp_path_, fs_permission_)); return {}; } @@ -143,11 +141,8 @@ Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_c if (ec) { // This should never happen. We were able to move the file from `original_path` to // `temp_path`. We should be able to move it back. - LOG(WARNING) << StringPrintf( - "Failed to move old file '%s' back from temporary path '%s': %s", - std::string(original_path).c_str(), - std::string(temp_path).c_str(), - ec.message().c_str()); + LOG(WARNING) << "Failed to move old file '{}' back from temporary path '{}': {}"_format( + original_path, temp_path, ec.message()); } } }); @@ -208,7 +203,7 @@ Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_c } std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) { - return StringPrintf("%s.%s.tmp", std::string(final_path).c_str(), id.c_str()); + return "{}.{}.tmp"_format(final_path, id); } Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) { @@ -219,10 +214,28 @@ Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) { return file; } -mode_t FsPermissionToMode(const FsPermission& fs_permission) { +mode_t FileFsPermissionToMode(const FsPermission& fs_permission) { return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) | (fs_permission.isOtherExecutable ? S_IXOTH : 0); } +mode_t DirFsPermissionToMode(const FsPermission& fs_permission) { + return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP; +} + +Result<void> Chown(const std::string& path, const FsPermission& fs_permission) { + if (fs_permission.uid < 0 && fs_permission.gid < 0) { + // Keep the default owner. + } else if (fs_permission.uid < 0 || fs_permission.gid < 0) { + return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.", + fs_permission.uid, + fs_permission.gid); + } + if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) { + return ErrnoErrorf("Failed to chown '{}'", path); + } + return {}; +} + } // namespace artd } // namespace art diff --git a/artd/file_utils.h b/artd/file_utils.h index 9dc41f4097..b5fd170676 100644 --- a/artd/file_utils.h +++ b/artd/file_utils.h @@ -119,8 +119,15 @@ class NewFile { // Opens a file for reading. android::base::Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path); -// Converts FsPermission to Linux access mode. -mode_t FsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission); +// Converts FsPermission to Linux access mode for a file. +mode_t FileFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission); + +// Converts FsPermission to Linux access mode for a directory. +mode_t DirFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission); + +// Changes the owner based on FsPermission. +android::base::Result<void> Chown( + const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission); } // namespace artd } // namespace art diff --git a/artd/file_utils_test.cc b/artd/file_utils_test.cc index e6219a4658..8f79d5dce9 100644 --- a/artd/file_utils_test.cc +++ b/artd/file_utils_test.cc @@ -325,14 +325,25 @@ TEST_F(FileUtilsTest, OpenFileForReadingFailed) { HasError(WithMessage(ContainsRegex("Failed to open file .*/foo")))); } -TEST_F(FileUtilsTest, FsPermissionToMode) { - EXPECT_EQ(FsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP); - EXPECT_EQ(FsPermissionToMode(FsPermission{.isOtherReadable = true}), +TEST_F(FileUtilsTest, FileFsPermissionToMode) { + EXPECT_EQ(FileFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP); + EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherReadable = true}), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - EXPECT_EQ(FsPermissionToMode(FsPermission{.isOtherExecutable = true}), + EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherExecutable = true}), S_IRUSR | S_IWUSR | S_IRGRP | S_IXOTH); - EXPECT_EQ(FsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}), - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH); + EXPECT_EQ( + FileFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}), + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH); +} + +TEST_F(FileUtilsTest, DirFsPermissionToMode) { + EXPECT_EQ(DirFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); + EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true}), + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH); + EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherExecutable = true}), + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH); + EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}), + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); } } // namespace diff --git a/artd/path_utils.cc b/artd/path_utils.cc index c4d9031220..802ced0512 100644 --- a/artd/path_utils.cc +++ b/artd/path_utils.cc @@ -24,6 +24,7 @@ #include "android-base/strings.h" #include "arch/instruction_set.h" #include "base/file_utils.h" +#include "fmt/format.h" #include "oat_file_assistant.h" namespace art { @@ -32,14 +33,21 @@ namespace artd { namespace { using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::VdexPath; using ::android::base::EndsWith; using ::android::base::Error; using ::android::base::Result; +using ::fmt::literals::operator""_format; // NOLINT + Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) { if (path_str.empty()) { return Errorf("Path is empty"); } + if (path_str.find('\0') != std::string::npos) { + return Errorf("Path '{}' has invalid character '\\0'", path_str); + } std::filesystem::path path(path_str); if (!path.is_absolute()) { return Errorf("Path '{}' is not an absolute path", path_str); @@ -50,6 +58,17 @@ Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) { return {}; } +Result<std::string> GetArtRootOrError() { + std::string error_msg; + std::string result = GetArtRootSafe(&error_msg); + if (!error_msg.empty()) { + return Error() << error_msg; + } + return result; +} + +} // namespace + Result<void> ValidateDexPath(const std::string& dex_path) { OR_RETURN(ValidateAbsoluteNormalPath(dex_path)); if (!EndsWith(dex_path, ".apk") && !EndsWith(dex_path, ".jar")) { @@ -58,14 +77,16 @@ Result<void> ValidateDexPath(const std::string& dex_path) { return {}; } -} // namespace +Result<std::string> BuildArtBinPath(const std::string& binary_name) { + return "{}/bin/{}"_format(OR_RETURN(GetArtRootOrError()), binary_name); +} Result<std::string> BuildOatPath(const ArtifactsPath& artifacts_path) { OR_RETURN(ValidateDexPath(artifacts_path.dexPath)); InstructionSet isa = GetInstructionSetFromString(artifacts_path.isa.c_str()); if (isa == InstructionSet::kNone) { - return Errorf("Instruction set '{}' is invalid", artifacts_path.isa.c_str()); + return Errorf("Instruction set '{}' is invalid", artifacts_path.isa); } std::string error_msg; @@ -86,12 +107,19 @@ Result<std::string> BuildOatPath(const ArtifactsPath& artifacts_path) { } } -std::string OatPathToVdexPath(const std::string& oat_path) { - return ReplaceFileExtension(oat_path, "vdex"); +Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) { + OR_RETURN(ValidateDexPath(dex_metadata_path.dexPath)); + return ReplaceFileExtension(dex_metadata_path.dexPath, "dm"); +} + +Result<std::string> BuildDexMetadataPath(const VdexPath& vdex_path) { + DCHECK(vdex_path.getTag() == VdexPath::dexMetadataPath); + return BuildDexMetadataPath(vdex_path.get<VdexPath::dexMetadataPath>()); } -std::string OatPathToArtPath(const std::string& oat_path) { - return ReplaceFileExtension(oat_path, "art"); +Result<std::string> BuildVdexPath(const VdexPath& vdex_path) { + DCHECK(vdex_path.getTag() == VdexPath::artifactsPath); + return OatPathToVdexPath(OR_RETURN(BuildOatPath(vdex_path.get<VdexPath::artifactsPath>()))); } } // namespace artd diff --git a/artd/path_utils.h b/artd/path_utils.h index 970143a9c7..41513876f1 100644 --- a/artd/path_utils.h +++ b/artd/path_utils.h @@ -19,19 +19,37 @@ #include "aidl/com/android/server/art/BnArtd.h" #include "android-base/result.h" +#include "base/file_utils.h" namespace art { namespace artd { +android::base::Result<void> ValidateDexPath(const std::string& dex_path); + +android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name); + // Returns the absolute path to the OAT file built from the `ArtifactsPath`. android::base::Result<std::string> BuildOatPath( const aidl::com::android::server::art::ArtifactsPath& artifacts_path); // Returns the path to the VDEX file that corresponds to the OAT file. -std::string OatPathToVdexPath(const std::string& oat_path); +inline std::string OatPathToVdexPath(const std::string& oat_path) { + return ReplaceFileExtension(oat_path, "vdex"); +} // Returns the path to the ART file that corresponds to the OAT file. -std::string OatPathToArtPath(const std::string& oat_path); +inline std::string OatPathToArtPath(const std::string& oat_path) { + return ReplaceFileExtension(oat_path, "art"); +} + +android::base::Result<std::string> BuildDexMetadataPath( + const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path); + +android::base::Result<std::string> BuildDexMetadataPath( + const aidl::com::android::server::art::VdexPath& vdex_path); + +android::base::Result<std::string> BuildVdexPath( + const aidl::com::android::server::art::VdexPath& vdex_path); } // namespace artd } // namespace art diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc index 9ce40c5d41..4162da33f6 100644 --- a/artd/path_utils_test.cc +++ b/artd/path_utils_test.cc @@ -26,12 +26,23 @@ namespace artd { namespace { using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::VdexPath; using ::android::base::testing::HasError; using ::android::base::testing::HasValue; using ::android::base::testing::WithMessage; +using std::literals::operator""s; // NOLINT + class PathUtilsTest : public CommonArtTest {}; +TEST_F(PathUtilsTest, BuildArtBinPath) { + auto scratch_dir = std::make_unique<ScratchDir>(); + auto art_root_env = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT"); + setenv("ANDROID_ART_ROOT", scratch_dir->GetPath().c_str(), /*overwrite=*/1); + EXPECT_THAT(BuildArtBinPath("foo"), HasValue(scratch_dir->GetPath() + "/bin/foo")); +} + TEST_F(PathUtilsTest, BuildOatPath) { EXPECT_THAT( BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}), @@ -61,6 +72,12 @@ TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) { HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form"))); } +TEST_F(PathUtilsTest, BuildOatPathNul) { + EXPECT_THAT(BuildOatPath(ArtifactsPath{ + .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}), + HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s))); +} + TEST_F(PathUtilsTest, BuildOatPathInvalidDexExtension) { EXPECT_THAT(BuildOatPath(ArtifactsPath{ .dexPath = "/a/b.invalid", .isa = "arm64", .isInDalvikCache = false}), @@ -81,6 +98,21 @@ TEST_F(PathUtilsTest, OatPathToArtPath) { EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art"); } +TEST_F(PathUtilsTest, BuildDexMetadataPath) { + EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm")); +} + +TEST_F(PathUtilsTest, BuildDexMetadataPathForVdex) { + EXPECT_THAT(BuildDexMetadataPath(VdexPath(DexMetadataPath{.dexPath = "/a/b.apk"})), + HasValue("/a/b.dm")); +} + +TEST_F(PathUtilsTest, BuildVdexPath) { + EXPECT_THAT( + BuildVdexPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasValue("/a/oat/arm64/b.vdex")); +} + } // namespace } // namespace artd } // namespace art |