ART services: optimize package - add profile-related artd methods.
This change adds a few artd methods that will be used for profile-guided
compilation.
Bug: 229268202
Test: m test-art-host-gtest-art_artd_tests
Ignore-AOSP-First: ART Services.
Change-Id: Ic80ecd3f77185041376e0744c021cd88feb88a4a
diff --git a/artd/Android.bp b/artd/Android.bp
index 2170289..e0674f0 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -30,6 +30,9 @@
"file_utils.cc",
"path_utils.cc",
],
+ header_libs: [
+ "profman_headers",
+ ],
shared_libs: [
"libarttools",
"libbase",
diff --git a/artd/artd.cc b/artd/artd.cc
index 0616e31..36f7111 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -23,6 +23,7 @@
#include <climits>
#include <cstdint>
+#include <cstring>
#include <filesystem>
#include <functional>
#include <map>
@@ -57,6 +58,7 @@
#include "oat_file_assistant.h"
#include "oat_file_assistant_context.h"
#include "path_utils.h"
+#include "profman/profman_result.h"
#include "selinux/android.h"
#include "tools/cmdline_builder.h"
#include "tools/tools.h"
@@ -69,10 +71,12 @@
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::FileVisibility;
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::OutputProfile;
using ::aidl::com::android::server::art::PriorityClass;
using ::aidl::com::android::server::art::ProfilePath;
using ::aidl::com::android::server::art::VdexPath;
@@ -80,18 +84,24 @@
using ::android::base::Error;
using ::android::base::Join;
using ::android::base::make_scope_guard;
+using ::android::base::ReadFileToString;
using ::android::base::Result;
using ::android::base::Split;
using ::android::base::StringReplace;
+using ::android::base::WriteStringToFd;
using ::art::tools::CmdlineBuilder;
using ::ndk::ScopedAStatus;
using ::fmt::literals::operator""_format; // NOLINT
using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation;
+using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
constexpr const char* kServiceName = "artd";
+// Timeout for short operations, such as merging profiles.
+constexpr int kShortTimeoutSec = 60; // 1 minute.
+
// 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
@@ -241,6 +251,22 @@
return {};
}
+Result<FileVisibility> GetFileVisibility(const std::string& file) {
+ std::error_code ec;
+ std::filesystem::file_status status = std::filesystem::status(file, ec);
+ if (!std::filesystem::status_known(status)) {
+ return Errorf("Failed to get status of '{}': {}", file, ec.message());
+ }
+ if (!std::filesystem::exists(status)) {
+ return FileVisibility::NOT_FOUND;
+ }
+
+ return (status.permissions() & std::filesystem::perms::others_read) !=
+ std::filesystem::perms::none ?
+ FileVisibility::OTHER_READABLE :
+ FileVisibility::NOT_OTHER_READABLE;
+}
+
class FdLogger {
public:
void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); }
@@ -328,6 +354,173 @@
return ScopedAStatus::ok();
}
+ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile,
+ const std::string& in_dexFile,
+ bool* _aidl_return) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+ CmdlineBuilder args;
+ FdLogger fd_logger;
+ args.Add(OR_RETURN_FATAL(GetArtExec()))
+ .Add("--drop-capabilities")
+ .Add("--")
+ .Add(OR_RETURN_FATAL(GetProfman()));
+
+ Result<std::unique_ptr<File>> profile = OpenFileForReading(profile_path);
+ if (!profile.ok()) {
+ if (profile.error().code() == ENOENT) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+ return NonFatal(
+ "Failed to open profile '{}': {}"_format(profile_path, profile.error().message()));
+ }
+ args.Add("--reference-profile-file-fd=%d", profile.value()->Fd());
+ fd_logger.Add(*profile.value());
+
+ std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+ args.Add("--apk-fd=%d", dex_file->Fd());
+ fd_logger.Add(*dex_file);
+
+ LOG(DEBUG) << "Running profman: " << Join(args.Get(), /*separator=*/" ")
+ << "\nOpened FDs: " << fd_logger;
+
+ Result<int> result = ExecAndReturnCode(args.Get(), kShortTimeoutSec);
+ if (!result.ok()) {
+ return NonFatal("Failed to run profman: " + result.error().message());
+ }
+
+ if (result.value() != ProfmanResult::kSkipCompilationSmallDelta &&
+ result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) {
+ return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+ }
+
+ *_aidl_return = result.value() == ProfmanResult::kSkipCompilationSmallDelta;
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::copyProfile(const ProfilePath& in_src, OutputProfile* in_dst) {
+ std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
+ if (in_src.getTag() == ProfilePath::dexMetadataPath) {
+ return Fatal("Does not support DM file, got '{}'"_format(src_path));
+ }
+ std::string dst_path = OR_RETURN_FATAL(BuildRefProfilePath(in_dst->profilePath.refProfilePath));
+
+ std::string content;
+ if (!ReadFileToString(src_path, &content)) {
+ return NonFatal("Failed to read file '{}': {}"_format(src_path, strerror(errno)));
+ }
+
+ std::unique_ptr<NewFile> dst =
+ OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission));
+ if (!WriteStringToFd(content, dst->Fd())) {
+ return NonFatal("Failed to write file '{}': {}"_format(dst_path, strerror(errno)));
+ }
+
+ OR_RETURN_NON_FATAL(dst->Keep());
+ in_dst->profilePath.id = dst->TempId();
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src,
+ OutputProfile* in_dst,
+ const std::string& in_dexFile,
+ bool* _aidl_return) {
+ std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
+ std::string dst_path = OR_RETURN_FATAL(BuildRefProfilePath(in_dst->profilePath.refProfilePath));
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+ CmdlineBuilder args;
+ FdLogger fd_logger;
+ args.Add(OR_RETURN_FATAL(GetArtExec()))
+ .Add("--drop-capabilities")
+ .Add("--")
+ .Add(OR_RETURN_FATAL(GetProfman()))
+ .Add("--copy-and-update-profile-key");
+
+ Result<std::unique_ptr<File>> src = OpenFileForReading(src_path);
+ if (!src.ok()) {
+ if (src.error().code() == ENOENT) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+ return NonFatal("Failed to open src profile '{}': {}"_format(src_path, src.error().message()));
+ }
+ args.Add("--profile-file-fd=%d", src.value()->Fd());
+ fd_logger.Add(*src.value());
+
+ std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+ args.Add("--apk-fd=%d", dex_file->Fd());
+ fd_logger.Add(*dex_file);
+
+ std::unique_ptr<NewFile> dst =
+ OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission));
+ args.Add("--reference-profile-file-fd=%d", dst->Fd());
+ fd_logger.Add(*dst);
+
+ LOG(DEBUG) << "Running profman: " << Join(args.Get(), /*separator=*/" ")
+ << "\nOpened FDs: " << fd_logger;
+
+ Result<int> result = ExecAndReturnCode(args.Get(), kShortTimeoutSec);
+ if (!result.ok()) {
+ return NonFatal("Failed to run profman: " + result.error().message());
+ }
+
+ if (result.value() == ProfmanResult::kCopyAndUpdateNoUpdate) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+
+ if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) {
+ return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+ }
+
+ OR_RETURN_NON_FATAL(dst->Keep());
+ *_aidl_return = true;
+ in_dst->profilePath.id = dst->TempId();
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::commitTmpProfile(const TmpRefProfilePath& in_profile) {
+ std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpRefProfilePath(in_profile));
+ std::string ref_profile_path = OR_RETURN_FATAL(BuildRefProfilePath(in_profile.refProfilePath));
+
+ std::error_code ec;
+ std::filesystem::rename(tmp_profile_path, ref_profile_path, ec);
+ if (ec) {
+ return NonFatal(
+ "Failed to move '{}' to '{}': {}"_format(tmp_profile_path, ref_profile_path, ec.message()));
+ }
+
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+
+ std::error_code ec;
+ if (!std::filesystem::remove(profile_path, ec)) {
+ LOG(ERROR) << "Failed to remove '{}': {}"_format(profile_path, ec.message());
+ }
+
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile,
+ FileVisibility* _aidl_return) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(profile_path));
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath,
+ FileVisibility* _aidl_return) {
+ std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path));
+ return ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile,
const std::string& in_instructionSet,
const std::string& in_classLoaderContext,
@@ -378,10 +571,10 @@
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::optional<std::string> profile_path =
+ in_profile.has_value() ?
+ std::make_optional(OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile.value()))) :
+ std::nullopt;
std::unique_ptr<ClassLoaderContext> context =
ClassLoaderContext::Create(in_classLoaderContext.c_str());
@@ -583,6 +776,8 @@
return cached_deny_art_apex_data_files_.value();
}
+Result<std::string> Artd::GetProfman() { return BuildArtBinPath("profman"); }
+
Result<std::string> Artd::GetArtExec() { return BuildArtBinPath("art_exec"); }
bool Artd::ShouldUseDex2Oat64() {
diff --git a/artd/artd.h b/artd/artd.h
index cf63135..d637a0b 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -55,6 +55,33 @@
const std::string& in_classLoaderContext,
aidl::com::android::server::art::GetOptimizationStatusResult* _aidl_return) override;
+ ndk::ScopedAStatus isProfileUsable(const aidl::com::android::server::art::ProfilePath& in_profile,
+ const std::string& in_dexFile,
+ bool* _aidl_return) override;
+
+ ndk::ScopedAStatus copyProfile(const aidl::com::android::server::art::ProfilePath& in_src,
+ ::aidl::com::android::server::art::OutputProfile* in_dst) override;
+
+ ndk::ScopedAStatus copyAndRewriteProfile(
+ const aidl::com::android::server::art::ProfilePath& in_src,
+ aidl::com::android::server::art::OutputProfile* in_dst,
+ const std::string& in_dexFile,
+ bool* _aidl_return) override;
+
+ ndk::ScopedAStatus commitTmpProfile(
+ const aidl::com::android::server::art::ProfilePath::TmpRefProfilePath& in_profile) override;
+
+ ndk::ScopedAStatus deleteProfile(
+ const aidl::com::android::server::art::ProfilePath& in_profile) override;
+
+ ndk::ScopedAStatus getProfileVisibility(
+ const aidl::com::android::server::art::ProfilePath& in_profile,
+ aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+ ndk::ScopedAStatus getArtifactsVisibility(
+ const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+ aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
ndk::ScopedAStatus getDexoptNeeded(
const std::string& in_dexFile,
const std::string& in_instructionSet,
@@ -92,6 +119,8 @@
android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector,
int timeout_sec) const;
+ android::base::Result<std::string> GetProfman();
+
android::base::Result<std::string> GetArtExec();
bool ShouldUseDex2Oat64();
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 8fd9e96..fcc15d4 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -41,6 +41,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "path_utils.h"
+#include "profman/profman_result.h"
#include "tools/system_properties.h"
namespace art {
@@ -50,9 +51,12 @@
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::FileVisibility;
using ::aidl::com::android::server::art::FsPermission;
using ::aidl::com::android::server::art::OutputArtifacts;
+using ::aidl::com::android::server::art::OutputProfile;
using ::aidl::com::android::server::art::PriorityClass;
+using ::aidl::com::android::server::art::ProfilePath;
using ::aidl::com::android::server::art::VdexPath;
using ::android::base::make_scope_guard;
using ::android::base::ParseInt;
@@ -70,12 +74,16 @@
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
+using ::testing::IsEmpty;
using ::testing::MockFunction;
using ::testing::Not;
using ::testing::ResultOf;
using ::testing::Return;
using ::testing::WithArg;
+using RefProfilePath = ProfilePath::RefProfilePath;
+using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+
using ::fmt::literals::operator""_format; // NOLINT
ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) {
@@ -190,6 +198,11 @@
std::filesystem::create_directories(art_root_);
setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1);
+ // Use an arbitrary existing directory as Android data.
+ android_data_ = scratch_path_ + "/data";
+ std::filesystem::create_directories(android_data_);
+ setenv("ANDROID_DATA", android_data_.c_str(), /*overwrite=*/1);
+
dex_file_ = scratch_path_ + "/a/b.apk";
isa_ = "arm64";
artifacts_path_ = ArtifactsPath{
@@ -222,6 +235,10 @@
clc_2_ = GetTestDexFileName("Nested");
class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_);
compiler_filter_ = "speed";
+ profile_path_ =
+ TmpRefProfilePath{.refProfilePath = RefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"},
+ .id = "12345"};
}
void TearDown() override {
@@ -237,7 +254,7 @@
isa_,
class_loader_context_,
compiler_filter_,
- /*in_profile=*/std::nullopt,
+ profile_path_,
vdex_path_,
priority_class_,
dexopt_options_,
@@ -248,12 +265,20 @@
}
}
+ void CreateFile(const std::string& filename, const std::string& content = "") {
+ std::filesystem::path path(filename);
+ std::filesystem::create_directories(path.parent_path());
+ WriteStringToFile(content, filename);
+ }
+
std::shared_ptr<Artd> artd_;
std::unique_ptr<ScratchDir> scratch_dir_;
std::string scratch_path_;
std::string art_root_;
+ std::string android_data_;
MockFunction<android::base::LogFunction> mock_logger_;
ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+ ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
MockSystemProperties* mock_props_;
MockExecUtils* mock_exec_utils_;
@@ -268,14 +293,9 @@
std::optional<VdexPath> vdex_path_;
PriorityClass priority_class_ = PriorityClass::BACKGROUND;
DexoptOptions dexopt_options_;
+ std::optional<ProfilePath> profile_path_;
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()) {
@@ -285,6 +305,9 @@
CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())));
}
}
+ if (profile_path_.has_value()) {
+ CreateFile(OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+ }
}
};
@@ -312,7 +335,7 @@
TEST_F(ArtdTest, deleteArtifactsMissingFile) {
// Missing VDEX file.
- std::string oat_dir = dalvik_cache_ + "/arm64";
+ std::string oat_dir = android_data_ + "/dalvik-cache/arm64";
std::filesystem::create_directories(oat_dir);
WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex"); // 4 bytes.
WriteStringToFile("a", oat_dir + "/a@b.apk@classes.art"); // 1 byte.
@@ -396,7 +419,11 @@
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"))))))
+ Contains(Flag("--compiler-filter=", "speed")),
+ Contains(Flag(
+ "--profile-file-fd=",
+ FdOf(android_data_ +
+ "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp")))))))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
Return(0)));
@@ -719,6 +746,291 @@
EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art"));
}
+TEST_F(ArtdTest, isProfileUsable) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(WhenSplitBy(
+ "--",
+ AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+ AllOf(Contains(art_root_ + "/bin/profman"),
+ Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))),
+ Contains(Flag("--apk-fd=", FdOf(dex_file_)))))))
+ .WillOnce(Return(ProfmanResult::kSkipCompilationSmallDelta));
+
+ bool result;
+ EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+ EXPECT_TRUE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableFalse) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
+ .WillOnce(Return(ProfmanResult::kSkipCompilationEmptyProfiles));
+
+ bool result;
+ EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+ EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableNotFound) {
+ CreateFile(dex_file_);
+
+ bool result;
+ EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+ EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableFailed) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_)).WillOnce(Return(100));
+
+ bool result;
+ ndk::ScopedAStatus status = artd_->isProfileUsable(profile_path_.value(), dex_file_, &result);
+
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+}
+
+TEST_F(ArtdTest, copyProfile) {
+ const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+ CreateFile(src_file, "abc");
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+
+ EXPECT_TRUE(artd_->copyProfile(src, &dst).isOk());
+
+ EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
+ CheckContent(OR_FATAL(BuildTmpRefProfilePath(dst.profilePath)), "abc");
+}
+
+TEST_F(ArtdTest, copyProfileFailed) {
+ const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+
+ ndk::ScopedAStatus status = artd_->copyProfile(src, &dst);
+
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(),
+ ContainsRegex(R"re(Failed to read file .*primary\.prof\.12345\.tmp)re"));
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfile) {
+ const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+ CreateFile(src_file, "abc");
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(WhenSplitBy(
+ "--",
+ AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+ AllOf(Contains(art_root_ + "/bin/profman"),
+ Contains("--copy-and-update-profile-key"),
+ Contains(Flag("--profile-file-fd=", FdOf(src_file))),
+ Contains(Flag("--apk-fd=", FdOf(dex_file_)))))))
+ .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
+ Return(ProfmanResult::kCopyAndUpdateSuccess)));
+
+ bool result;
+ EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
+ CheckContent(OR_FATAL(BuildTmpRefProfilePath(dst.profilePath)), "def");
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFalse) {
+ const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+ CreateFile(src_file, "abc");
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
+ .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoUpdate));
+
+ bool result;
+ EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+ EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileNotFound) {
+ CreateFile(dex_file_);
+
+ const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+
+ bool result;
+ EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+ EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFailed) {
+ const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+ CreateFile(src_file, "abc");
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_)).WillOnce(Return(100));
+
+ bool result;
+ ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result);
+
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+}
+
+TEST_F(ArtdTest, commitTmpProfile) {
+ const TmpRefProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ std::string tmp_profile_file = OR_FATAL(BuildTmpRefProfilePath(tmp_profile_path));
+ CreateFile(tmp_profile_file);
+
+ EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk());
+
+ EXPECT_FALSE(std::filesystem::exists(tmp_profile_file));
+ EXPECT_TRUE(
+ std::filesystem::exists(OR_FATAL(BuildProfileOrDmPath(tmp_profile_path.refProfilePath))));
+}
+
+TEST_F(ArtdTest, commitTmpProfileFailed) {
+ const TmpRefProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+ ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path);
+
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(
+ status.getMessage(),
+ ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re"));
+
+ EXPECT_FALSE(
+ std::filesystem::exists(OR_FATAL(BuildProfileOrDmPath(tmp_profile_path.refProfilePath))));
+}
+
+TEST_F(ArtdTest, deleteProfile) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+
+ EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+
+ EXPECT_FALSE(std::filesystem::exists(profile_file));
+}
+
+TEST_F(ArtdTest, deleteProfileFailed) {
+ auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+ EXPECT_CALL(
+ mock_logger_,
+ Call(_, _, _, _, _, ContainsRegex(R"re(Failed to remove .*primary\.prof\.12345\.tmp)re")));
+
+ EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+TEST_F(ArtdTest, getProfileVisibilityOtherReadable) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+ std::filesystem::permissions(
+ profile_file, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getProfileVisibility(profile_path_.value(), &result).isOk());
+ EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+}
+
+TEST_F(ArtdTest, getProfileVisibilityNotOtherReadable) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+ std::filesystem::permissions(
+ profile_file, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getProfileVisibility(profile_path_.value(), &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+}
+
+TEST_F(ArtdTest, getProfileVisibilityNotFound) {
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getProfileVisibility(profile_path_.value(), &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+}
+
+TEST_F(ArtdTest, getProfileVisibilityPermissionDenied) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+
+ auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(profile_file).parent_path());
+ auto scoped_unroot = ScopedUnroot();
+
+ FileVisibility result;
+ ndk::ScopedAStatus status = artd_->getProfileVisibility(profile_path_.value(), &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(),
+ ContainsRegex(R"re(Failed to get status of .*primary\.prof\.12345\.tmp)re"));
+}
+
+TEST_F(ArtdTest, getArtifactsVisibilityOtherReadable) {
+ std::string oat_file = OR_FATAL(BuildOatPath(artifacts_path_));
+ CreateFile(oat_file);
+ std::filesystem::permissions(
+ oat_file, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getArtifactsVisibility(artifacts_path_, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+}
+
+TEST_F(ArtdTest, getArtifactsVisibilityNotOtherReadable) {
+ std::string oat_file = OR_FATAL(BuildOatPath(artifacts_path_));
+ CreateFile(oat_file);
+ std::filesystem::permissions(
+ oat_file, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getArtifactsVisibility(artifacts_path_, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+}
+
+TEST_F(ArtdTest, getArtifactsVisibilityNotFound) {
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getArtifactsVisibility(artifacts_path_, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+}
+
+TEST_F(ArtdTest, getArtifactsVisibilityPermissionDenied) {
+ std::string oat_file = OR_FATAL(BuildOatPath(artifacts_path_));
+ CreateFile(oat_file);
+
+ auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(oat_file).parent_path());
+ auto scoped_unroot = ScopedUnroot();
+
+ FileVisibility result;
+ ndk::ScopedAStatus status = artd_->getArtifactsVisibility(artifacts_path_, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(), ContainsRegex(R"re(Failed to get status of .*b\.odex)re"));
+}
+
} // namespace
} // namespace artd
} // namespace art
diff --git a/artd/binder/com/android/server/art/FileVisibility.aidl b/artd/binder/com/android/server/art/FileVisibility.aidl
new file mode 100644
index 0000000..c8d1455
--- /dev/null
+++ b/artd/binder/com/android/server/art/FileVisibility.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;
+
+/**
+ * Indicates the visibility of a file. I.e., whether the file has the "read" bit for "others"
+ * (S_IROTH).
+ *
+ * Theoretically, even if the value is {@code OTHER_READABLE}, others' access can still be denied
+ * due to the lack of the "exec" bit on parent directories. However, for compilation artifacts, all
+ * parent directories do have the "exec" bit for "others" in practice.
+ *
+ * @hide
+ */
+enum FileVisibility {
+ NOT_FOUND = 0,
+ OTHER_READABLE = 1,
+ NOT_OTHER_READABLE = 2,
+}
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 47c1ade..9328dab 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -38,6 +38,68 @@
@utf8InCpp String classLoaderContext);
/**
+ * Returns true if the profile exists and contains entries for the given dex file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ boolean isProfileUsable(in com.android.server.art.ProfilePath profile,
+ @utf8InCpp String dexFile);
+
+ /**
+ * Copies the profile. Throws if `src` does not exist. Fills `dst.profilePath.id` on success.
+ *
+ * Does not operate on a DM file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ void copyProfile(in com.android.server.art.ProfilePath src,
+ inout com.android.server.art.OutputProfile dst);
+
+ /**
+ * Copies the profile and rewrites it for the given dex file. Returns true and fills
+ * `dst.profilePath.id` if the operation succeeds and `src` exists and contains entries that
+ * match the given dex file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ boolean copyAndRewriteProfile(in com.android.server.art.ProfilePath src,
+ inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile);
+
+ /**
+ * Moves the temporary profile to the permanent location.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ void commitTmpProfile(in com.android.server.art.ProfilePath.TmpRefProfilePath profile);
+
+ /**
+ * Deletes the profile.
+ *
+ * Operates on the whole DM file if given one.
+ *
+ * Throws fatal errors. Logs and ignores non-fatal errors.
+ */
+ void deleteProfile(in com.android.server.art.ProfilePath profile);
+
+ /**
+ * Returns the visibility of the profile.
+ *
+ * Operates on the whole DM file if given one.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ com.android.server.art.FileVisibility getProfileVisibility(
+ in com.android.server.art.ProfilePath profile);
+
+ /**
+ * Returns the visibility of the artifacts.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ com.android.server.art.FileVisibility getArtifactsVisibility(
+ in com.android.server.art.ArtifactsPath artifactsPath);
+
+ /**
* Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values
* defined in `com.android.server.art.DexoptTrigger`.
*
diff --git a/artd/binder/com/android/server/art/OutputProfile.aidl b/artd/binder/com/android/server/art/OutputProfile.aidl
new file mode 100644
index 0000000..cd9627b
--- /dev/null
+++ b/artd/binder/com/android/server/art/OutputProfile.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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 profile file.
+ *
+ * @hide
+ */
+parcelable OutputProfile {
+ /**
+ * The path to the output.
+ *
+ * Only outputing to a temporary file is supported to avoid race condition.
+ */
+ com.android.server.art.ProfilePath.TmpRefProfilePath profilePath;
+
+ /** The permission of the file. */
+ com.android.server.art.FsPermission fsPermission;
+}
diff --git a/artd/binder/com/android/server/art/ProfilePath.aidl b/artd/binder/com/android/server/art/ProfilePath.aidl
index d9e1208..3846b06 100644
--- a/artd/binder/com/android/server/art/ProfilePath.aidl
+++ b/artd/binder/com/android/server/art/ProfilePath.aidl
@@ -21,5 +21,32 @@
*
* @hide
*/
-parcelable ProfilePath {
+union ProfilePath {
+ RefProfilePath refProfilePath;
+ TmpRefProfilePath tmpRefProfilePath;
+ PrebuiltProfilePath prebuiltProfilePath;
+ /** Represents a profile in the dex metadata file. */
+ com.android.server.art.DexMetadataPath dexMetadataPath;
+
+ /** Represents a reference profile. */
+ parcelable RefProfilePath {
+ /** The name of the package. */
+ @utf8InCpp String packageName;
+ /** The stem of the profile file */
+ @utf8InCpp String profileName;
+ }
+
+ /** Represents a temporary reference profile. */
+ parcelable TmpRefProfilePath {
+ /** The reference profile that this temporary file is for. */
+ RefProfilePath refProfilePath;
+ /** A unique identifier to distinguish this temporary file from others. Filled by artd. */
+ @utf8InCpp String id;
+ }
+
+ /** Represents a profile built in the system image. */
+ parcelable PrebuiltProfilePath {
+ /** The path to the dex file that the prebuilt profile is next to. */
+ @utf8InCpp String dexPath;
+ }
}
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
index 802ced0..0cebdfb 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 "file_utils.h"
#include "fmt/format.h"
#include "oat_file_assistant.h"
@@ -34,6 +35,7 @@
using ::aidl::com::android::server::art::ArtifactsPath;
using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
using ::aidl::com::android::server::art::VdexPath;
using ::android::base::EndsWith;
using ::android::base::Error;
@@ -41,6 +43,10 @@
using ::fmt::literals::operator""_format; // NOLINT
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using RefProfilePath = ProfilePath::RefProfilePath;
+using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+
Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
if (path_str.empty()) {
return Errorf("Path is empty");
@@ -58,6 +64,37 @@
return {};
}
+Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
+ const std::string& name) {
+ if (path_element_substring.empty()) {
+ return Errorf("{} is empty", name);
+ }
+ if (path_element_substring.find('/') != std::string::npos) {
+ return Errorf("{} '{}' has invalid character '/'", name, path_element_substring);
+ }
+ if (path_element_substring.find('\0') != std::string::npos) {
+ return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring);
+ }
+ return {};
+}
+
+Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) {
+ OR_RETURN(ValidatePathElementSubstring(path_element, name));
+ if (path_element == "." || path_element == "..") {
+ return Errorf("Invalid {} '{}'", name, path_element);
+ }
+ return {};
+}
+
+Result<std::string> GetAndroidDataOrError() {
+ std::string error_msg;
+ std::string result = GetAndroidDataSafe(&error_msg);
+ if (!error_msg.empty()) {
+ return Error() << error_msg;
+ }
+ return result;
+}
+
Result<std::string> GetArtRootOrError() {
std::string error_msg;
std::string result = GetArtRootSafe(&error_msg);
@@ -107,6 +144,26 @@
}
}
+Result<std::string> BuildRefProfilePath(const RefProfilePath& ref_profile_path) {
+ OR_RETURN(ValidatePathElement(ref_profile_path.packageName, "packageName"));
+ OR_RETURN(ValidatePathElementSubstring(ref_profile_path.profileName, "profileName"));
+ return "{}/misc/profiles/ref/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+ ref_profile_path.packageName,
+ ref_profile_path.profileName);
+}
+
+Result<std::string> BuildTmpRefProfilePath(const TmpRefProfilePath& tmp_ref_profile_path) {
+ OR_RETURN(ValidatePathElementSubstring(tmp_ref_profile_path.id, "id"));
+ return NewFile::BuildTempPath(
+ OR_RETURN(BuildRefProfilePath(tmp_ref_profile_path.refProfilePath)).c_str(),
+ tmp_ref_profile_path.id.c_str());
+}
+
+Result<std::string> BuildPrebuiltProfilePath(const PrebuiltProfilePath& prebuilt_profile_path) {
+ OR_RETURN(ValidateDexPath(prebuilt_profile_path.dexPath));
+ return prebuilt_profile_path.dexPath + ".prof";
+}
+
Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) {
OR_RETURN(ValidateDexPath(dex_metadata_path.dexPath));
return ReplaceFileExtension(dex_metadata_path.dexPath, "dm");
@@ -117,6 +174,22 @@
return BuildDexMetadataPath(vdex_path.get<VdexPath::dexMetadataPath>());
}
+Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) {
+ switch (profile_path.getTag()) {
+ case ProfilePath::refProfilePath:
+ return BuildRefProfilePath(profile_path.get<ProfilePath::refProfilePath>());
+ case ProfilePath::tmpRefProfilePath:
+ return BuildTmpRefProfilePath(profile_path.get<ProfilePath::tmpRefProfilePath>());
+ case ProfilePath::prebuiltProfilePath:
+ return BuildPrebuiltProfilePath(profile_path.get<ProfilePath::prebuiltProfilePath>());
+ case ProfilePath::dexMetadataPath:
+ return BuildDexMetadataPath(profile_path.get<ProfilePath::dexMetadataPath>());
+ // 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 profile path type {}"_format(profile_path.getTag());
+}
+
Result<std::string> BuildVdexPath(const VdexPath& vdex_path) {
DCHECK(vdex_path.getTag() == VdexPath::artifactsPath);
return OatPathToVdexPath(OR_RETURN(BuildOatPath(vdex_path.get<VdexPath::artifactsPath>())));
diff --git a/artd/path_utils.h b/artd/path_utils.h
index 4151387..f3a6359 100644
--- a/artd/path_utils.h
+++ b/artd/path_utils.h
@@ -42,12 +42,24 @@
return ReplaceFileExtension(oat_path, "art");
}
+android::base::Result<std::string> BuildRefProfilePath(
+ const aidl::com::android::server::art::ProfilePath::RefProfilePath& ref_profile_path);
+
+android::base::Result<std::string> BuildTmpRefProfilePath(
+ const aidl::com::android::server::art::ProfilePath::TmpRefProfilePath& tmp_ref_profile_path);
+
+android::base::Result<std::string> BuildPrebuiltProfilePath(
+ const aidl::com::android::server::art::ProfilePath::PrebuiltProfilePath& prebuilt_profile_path);
+
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> BuildProfileOrDmPath(
+ const aidl::com::android::server::art::ProfilePath& profile_path);
+
android::base::Result<std::string> BuildVdexPath(
const aidl::com::android::server::art::VdexPath& vdex_path);
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
index 4162da3..29a9cb8 100644
--- a/artd/path_utils_test.cc
+++ b/artd/path_utils_test.cc
@@ -27,11 +27,16 @@
using ::aidl::com::android::server::art::ArtifactsPath;
using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
using ::aidl::com::android::server::art::VdexPath;
using ::android::base::testing::HasError;
using ::android::base::testing::HasValue;
using ::android::base::testing::WithMessage;
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using RefProfilePath = ProfilePath::RefProfilePath;
+using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+
using std::literals::operator""s; // NOLINT
class PathUtilsTest : public CommonArtTest {};
@@ -98,6 +103,89 @@
EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art");
}
+TEST_F(PathUtilsTest, BuildRefProfilePath) {
+ EXPECT_THAT(BuildRefProfilePath(
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildRefProfilePathPackageNameOk) {
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "...", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof"));
+ EXPECT_THAT(BuildRefProfilePath(
+ RefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildRefProfilePathPackageNameWrong) {
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "", .profileName = "primary"}),
+ HasError(WithMessage("packageName is empty")));
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = ".", .profileName = "primary"}),
+ HasError(WithMessage("Invalid packageName '.'")));
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "..", .profileName = "primary"}),
+ HasError(WithMessage("Invalid packageName '..'")));
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "a/b", .profileName = "primary"}),
+ HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
+ HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildRefProfilePathProfileNameOk) {
+ EXPECT_THAT(
+ BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
+ EXPECT_THAT(
+ BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
+ EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo",
+ .profileName = "!@#$%^&*()_+-="}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildRefProfilePathProfileNameWrong) {
+ EXPECT_THAT(
+ BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
+ HasError(WithMessage("profileName is empty")));
+ EXPECT_THAT(
+ BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
+ HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
+ EXPECT_THAT(
+ BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
+ HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildTmpRefProfilePath) {
+ EXPECT_THAT(
+ BuildTmpRefProfilePath(TmpRefProfilePath{
+ .refProfilePath =
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpRefProfilePathIdWrong) {
+ EXPECT_THAT(BuildTmpRefProfilePath(TmpRefProfilePath{
+ .refProfilePath =
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = ""}),
+ HasError(WithMessage("id is empty")));
+ EXPECT_THAT(BuildTmpRefProfilePath(TmpRefProfilePath{
+ .refProfilePath =
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "123/45"}),
+ HasError(WithMessage("id '123/45' has invalid character '/'")));
+ EXPECT_THAT(BuildTmpRefProfilePath(TmpRefProfilePath{
+ .refProfilePath =
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "123\0a"s}),
+ HasError(WithMessage("id '123\0a' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) {
+ EXPECT_THAT(BuildPrebuiltProfilePath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+ HasValue("/a/b.apk.prof"));
+}
+
TEST_F(PathUtilsTest, BuildDexMetadataPath) {
EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
}
@@ -107,6 +195,21 @@
HasValue("/a/b.dm"));
}
+TEST_F(PathUtilsTest, BuildProfilePath) {
+ EXPECT_THAT(BuildProfileOrDmPath(
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+ EXPECT_THAT(
+ BuildProfileOrDmPath(TmpRefProfilePath{
+ .refProfilePath =
+ RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+ EXPECT_THAT(BuildProfileOrDmPath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+ HasValue("/a/b.apk.prof"));
+ EXPECT_THAT(BuildProfileOrDmPath(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}),