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}),