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
diff --git a/artd/Android.bp b/artd/Android.bp
index 58680a5..2170289 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -34,6 +34,7 @@
"libarttools",
"libbase",
"libbinder_ndk",
+ "libselinux",
],
static_libs: [
"artd-aidl-ndk",
@@ -75,6 +76,10 @@
"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_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 0000000..9125046
--- /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 fc99b2a..2cdca3c 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 {
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 @@
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 @@
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 @@
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 @@
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 25a4c86..54d6776 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 @@
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 @@
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 @@
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 @@
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 129e31c..8fd9e96 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 @@
});
}
+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 @@
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, 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 @@
// 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 @@
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 @@
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 @@
.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 @@
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 0000000..5f9ab81
--- /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 0000000..351c079
--- /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 0000000..a160f22
--- /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 229f182..9c2ddb9 100644
--- a/artd/binder/com/android/server/art/FsPermission.aidl
+++ b/artd/binder/com/android/server/art/FsPermission.aidl
@@ -19,6 +19,8 @@
/**
* 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 0000000..99c4951
--- /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 5cf23b9..6750d9c 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -36,4 +36,29 @@
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 0000000..20ed475
--- /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 0000000..d9e1208
--- /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 0000000..ec23a0e
--- /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 67185b8..45344e1 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 @@
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 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 9dc41f4..b5fd170 100644
--- a/artd/file_utils.h
+++ b/artd/file_utils.h
@@ -119,8 +119,15 @@
// 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 e6219a4..8f79d5d 100644
--- a/artd/file_utils_test.cc
+++ b/artd/file_utils_test.cc
@@ -325,14 +325,25 @@
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 c4d9031..802ced0 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 {
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 @@
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 @@
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 @@
}
}
-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");
}
-std::string OatPathToArtPath(const std::string& oat_path) {
- return ReplaceFileExtension(oat_path, "art");
+Result<std::string> BuildDexMetadataPath(const VdexPath& vdex_path) {
+ DCHECK(vdex_path.getTag() == VdexPath::dexMetadataPath);
+ return BuildDexMetadataPath(vdex_path.get<VdexPath::dexMetadataPath>());
+}
+
+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 970143a..4151387 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 9ce40c5..4162da3 100644
--- a/artd/path_utils_test.cc
+++ b/artd/path_utils_test.cc
@@ -26,12 +26,23 @@
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 @@
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 @@
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