Add an artd method to copy the embedded profile from an apk file.

Context: go/platform-support-for-baseline-profiles

Bug: 257532944
Test: atest art_standalone_artd_tests
Change-Id: I16d638aa3f281fa0bdb437bf6a27826795394a86
Merged-In: I16d638aa3f281fa0bdb437bf6a27826795394a86
diff --git a/artd/Android.bp b/artd/Android.bp
index 6de6aef..5449cd9 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -39,6 +39,7 @@
         "libarttools", // Contains "libc++fs".
         "libbase",
         "libbinder_ndk",
+        "libdexfile",
         "libselinux",
     ],
     static_libs: [
diff --git a/artd/artd.cc b/artd/artd.cc
index 8b46d91..b96701c 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -25,6 +25,7 @@
 
 #include <climits>
 #include <csignal>
+#include <cstddef>
 #include <cstdint>
 #include <cstring>
 #include <filesystem>
@@ -62,9 +63,12 @@
 #include "base/globals.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/mem_map.h"
+#include "base/memfd.h"
 #include "base/os.h"
 #include "base/zip_archive.h"
 #include "cmdline_types.h"
+#include "dex/dex_file_loader.h"
 #include "exec_utils.h"
 #include "file_utils.h"
 #include "fstab/fstab.h"
@@ -101,6 +105,7 @@
 using ::aidl::com::android::server::art::RuntimeArtifactsPath;
 using ::aidl::com::android::server::art::VdexPath;
 using ::android::base::Dirname;
+using ::android::base::ErrnoError;
 using ::android::base::Error;
 using ::android::base::Join;
 using ::android::base::make_scope_guard;
@@ -398,6 +403,74 @@
   return bad_profile("The profile is in the wrong format or an I/O error has occurred");
 }
 
+// Returns the fd on success, or an invalid fd if the dex file contains no profile, or error if any
+// error occurs.
+Result<File> ExtractEmbeddedProfileToFd(const std::string& dex_path) {
+  std::unique_ptr<File> dex_file = OR_RETURN(OpenFileForReading(dex_path));
+
+  std::string error_msg;
+  uint32_t magic;
+  if (!ReadMagicAndReset(dex_file->Fd(), &magic, &error_msg)) {
+    return Error() << error_msg;
+  }
+  if (!IsZipMagic(magic)) {
+    if (DexFileLoader::IsMagicValid(magic)) {
+      // The dex file can be a plain dex file. This is expected.
+      return File();
+    }
+    return Error() << "File is neither a zip file nor a plain dex file";
+  }
+
+  std::unique_ptr<ZipArchive> zip_archive(
+      ZipArchive::OpenFromOwnedFd(dex_file->Fd(), dex_path.c_str(), &error_msg));
+  if (zip_archive == nullptr) {
+    return Error() << error_msg;
+  }
+  constexpr const char* kEmbeddedProfileEntry = "assets/art-profile/baseline.prof";
+  std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(kEmbeddedProfileEntry, &error_msg));
+  size_t size;
+  if (zip_entry == nullptr || (size = zip_entry->GetUncompressedLength()) == 0) {
+    // From system/libziparchive/zip_error.cpp.
+    constexpr const char* kEntryNotFound = "Entry not found";
+    if (error_msg != kEntryNotFound) {
+      LOG(WARNING) << ART_FORMAT(
+          "Failed to find zip entry '{}' in '{}': {}", kEmbeddedProfileEntry, dex_path, error_msg);
+    }
+    // The dex file doesn't necessarily contain a profile. This is expected.
+    return File();
+  }
+
+  // The name is for debugging only.
+  std::string memfd_name =
+      ART_FORMAT("{} extracted in memory from {}", kEmbeddedProfileEntry, dex_path);
+  File memfd(memfd_create(memfd_name.c_str(), /*flags=*/0),
+             memfd_name,
+             /*check_usage=*/false);
+  if (!memfd.IsValid()) {
+    return ErrnoError() << "Failed to create memfd";
+  }
+  if (ftruncate(memfd.Fd(), size) != 0) {
+    return ErrnoError() << "Failed to ftruncate memfd";
+  }
+  // Map with MAP_SHARED because we're feeding the fd to profman.
+  MemMap mem_map = MemMap::MapFile(size,
+                                   PROT_READ | PROT_WRITE,
+                                   MAP_SHARED,
+                                   memfd.Fd(),
+                                   /*start=*/0,
+                                   /*low_4gb=*/false,
+                                   memfd_name.c_str(),
+                                   &error_msg);
+  if (!mem_map.IsValid()) {
+    return Errorf("Failed to mmap memfd: {}", error_msg);
+  }
+  if (!zip_entry->ExtractToMemory(mem_map.Begin(), &error_msg)) {
+    return Errorf("Failed to extract '{}': {}", kEmbeddedProfileEntry, error_msg);
+  }
+
+  return memfd;
+}
+
 class FdLogger {
  public:
   void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); }
@@ -545,13 +618,12 @@
   return ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src,
-                                               OutputProfile* in_dst,
-                                               const std::string& in_dexFile,
-                                               CopyAndRewriteProfileResult* _aidl_return) {
-  std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
-  std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath));
-  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+ndk::ScopedAStatus Artd::CopyAndRewriteProfileImpl(File src,
+                                                   OutputProfile* dst_aidl,
+                                                   const std::string& dex_path,
+                                                   CopyAndRewriteProfileResult* aidl_return) {
+  std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(dst_aidl->profilePath));
+  OR_RETURN_FATAL(ValidateDexPath(dex_path));
 
   FdLogger fd_logger;
 
@@ -561,24 +633,15 @@
   CmdlineBuilder args;
   args.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->status = CopyAndRewriteProfileResult::Status::NO_PROFILE;
-      return ScopedAStatus::ok();
-    }
-    return NonFatal(
-        ART_FORMAT("Failed to open src profile '{}': {}", src_path, src.error().message()));
-  }
-  args.Add("--profile-file-fd=%d", src.value()->Fd());
-  fd_logger.Add(*src.value());
+  args.Add("--profile-file-fd=%d", src.Fd());
+  fd_logger.Add(src);
 
-  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path));
   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));
+      OR_RETURN_NON_FATAL(NewFile::Create(dst_path, dst_aidl->fsPermission));
   args.Add("--reference-profile-file-fd=%d", dst->Fd());
   fd_logger.Add(*dst);
 
@@ -596,8 +659,8 @@
 
   if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch ||
       result.value() == ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile) {
-    *_aidl_return = AnalyzeCopyAndRewriteProfileFailure(
-        src->get(), static_cast<ProfmanResult::CopyAndUpdateResult>(result.value()));
+    *aidl_return = AnalyzeCopyAndRewriteProfileFailure(
+        &src, static_cast<ProfmanResult::CopyAndUpdateResult>(result.value()));
     return ScopedAStatus::ok();
   }
 
@@ -606,12 +669,49 @@
   }
 
   OR_RETURN_NON_FATAL(dst->Keep());
-  _aidl_return->status = CopyAndRewriteProfileResult::Status::SUCCESS;
-  in_dst->profilePath.id = dst->TempId();
-  in_dst->profilePath.tmpPath = dst->TempPath();
+  aidl_return->status = CopyAndRewriteProfileResult::Status::SUCCESS;
+  dst_aidl->profilePath.id = dst->TempId();
+  dst_aidl->profilePath.tmpPath = dst->TempPath();
   return ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src,
+                                               OutputProfile* in_dst,
+                                               const std::string& in_dexFile,
+                                               CopyAndRewriteProfileResult* _aidl_return) {
+  std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
+
+  Result<std::unique_ptr<File>> src = OpenFileForReading(src_path);
+  if (!src.ok()) {
+    if (src.error().code() == ENOENT) {
+      _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE;
+      return ScopedAStatus::ok();
+    }
+    return NonFatal(
+        ART_FORMAT("Failed to open src profile '{}': {}", src_path, src.error().message()));
+  }
+
+  return CopyAndRewriteProfileImpl(std::move(*src.value()), in_dst, in_dexFile, _aidl_return);
+}
+
+ndk::ScopedAStatus Artd::copyAndRewriteEmbeddedProfile(OutputProfile* in_dst,
+                                                       const std::string& in_dexFile,
+                                                       CopyAndRewriteProfileResult* _aidl_return) {
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+  Result<File> src = ExtractEmbeddedProfileToFd(in_dexFile);
+  if (!src.ok()) {
+    return NonFatal(ART_FORMAT(
+        "Failed to extract profile from dex file '{}': {}", in_dexFile, src.error().message()));
+  }
+  if (!src->IsValid()) {
+    _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE;
+    return ScopedAStatus::ok();
+  }
+
+  return CopyAndRewriteProfileImpl(std::move(src.value()), in_dst, in_dexFile, _aidl_return);
+}
+
 ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) {
   std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile));
   std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile));
@@ -1181,6 +1281,7 @@
 
 Result<void> Artd::Start() {
   OR_RETURN(SetLogVerbosity());
+  MemMap::Init();
 
   ScopedAStatus status = ScopedAStatus::fromStatus(
       AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
diff --git a/artd/artd.h b/artd/artd.h
index c78aaa9..c670847 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -101,6 +101,11 @@
       const std::string& in_dexFile,
       aidl::com::android::server::art::CopyAndRewriteProfileResult* _aidl_return) override;
 
+  ndk::ScopedAStatus copyAndRewriteEmbeddedProfile(
+      aidl::com::android::server::art::OutputProfile* in_dst,
+      const std::string& in_dexFile,
+      aidl::com::android::server::art::CopyAndRewriteProfileResult* _aidl_return) override;
+
   ndk::ScopedAStatus commitTmpProfile(
       const aidl::com::android::server::art::ProfilePath::TmpProfilePath& in_profile) override;
 
@@ -221,6 +226,12 @@
 
   android::base::Result<struct stat> Fstat(const art::File& file) const;
 
+  ndk::ScopedAStatus CopyAndRewriteProfileImpl(
+      File src,
+      aidl::com::android::server::art::OutputProfile* dst_aidl,
+      const std::string& dex_path,
+      aidl::com::android::server::art::CopyAndRewriteProfileResult* aidl_return);
+
   std::mutex cache_mu_;
   std::optional<std::vector<std::string>> cached_boot_image_locations_ GUARDED_BY(cache_mu_);
   std::optional<std::vector<std::string>> cached_boot_class_path_ GUARDED_BY(cache_mu_);
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 4af8f17..6b93cee 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -26,6 +26,7 @@
 #include <condition_variable>
 #include <csignal>
 #include <cstdio>
+#include <cstring>
 #include <filesystem>
 #include <functional>
 #include <memory>
@@ -120,6 +121,8 @@
 using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
 using TmpProfilePath = ProfilePath::TmpProfilePath;
 
+using std::literals::operator""s;  // NOLINT
+
 ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) {
   android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger));
   return make_scope_guard([old_logger = std::move(old_logger)]() mutable {
@@ -429,11 +432,14 @@
     }
   }
 
+  template <bool kExpectOk>
+  using RunCopyAndRewriteProfileResult = Result<
+      std::pair<std::conditional_t<kExpectOk, CopyAndRewriteProfileResult, ndk::ScopedAStatus>,
+                OutputProfile>>;
+
   // Runs `copyAndRewriteProfile` with `tmp_profile_path_` and `dex_file_`.
   template <bool kExpectOk = true>
-  Result<std::pair<std::conditional_t<kExpectOk, CopyAndRewriteProfileResult, ndk::ScopedAStatus>,
-                   OutputProfile>>
-  RunCopyAndRewriteProfile() {
+  RunCopyAndRewriteProfileResult<kExpectOk> RunCopyAndRewriteProfile() {
     OutputProfile dst{.profilePath = tmp_profile_path_,
                       .fsPermission = FsPermission{.uid = -1, .gid = -1}};
     dst.profilePath.id = "";
@@ -452,6 +458,26 @@
     }
   }
 
+  // Runs `copyAndRewriteEmbeddedProfile` with `dex_file_`.
+  template <bool kExpectOk = true>
+  RunCopyAndRewriteProfileResult<kExpectOk> RunCopyAndRewriteEmbeddedProfile() {
+    OutputProfile dst{.profilePath = tmp_profile_path_,
+                      .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+    dst.profilePath.id = "";
+    dst.profilePath.tmpPath = "";
+
+    CopyAndRewriteProfileResult result;
+    ndk::ScopedAStatus status = artd_->copyAndRewriteEmbeddedProfile(&dst, dex_file_, &result);
+    if constexpr (kExpectOk) {
+      if (!status.isOk()) {
+        return Error() << status.getMessage();
+      }
+      return std::make_pair(std::move(result), std::move(dst));
+    } else {
+      return std::make_pair(std::move(status), std::move(dst));
+    }
+  }
+
   void CreateFile(const std::string& filename, const std::string& content = "") {
     std::filesystem::path path(filename);
     std::filesystem::create_directories(path.parent_path());
@@ -461,8 +487,10 @@
   void CreateZipWithSingleEntry(const std::string& filename,
                                 const std::string& entry_name,
                                 const std::string& content = "") {
+    std::filesystem::path path(filename);
+    std::filesystem::create_directories(path.parent_path());
     std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(filename.c_str()));
-    ASSERT_NE(file, nullptr);
+    ASSERT_NE(file, nullptr) << strerror(errno);
     file->MarkUnchecked();  // `writer.Finish()` flushes the file and the destructor closes it.
     ZipWriter writer(fdopen(file->Fd(), "wb"));
     ASSERT_EQ(writer.StartEntry(entry_name, /*flags=*/0), 0);
@@ -1534,6 +1562,85 @@
   EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
 }
 
+TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileSuccess) {
+  CreateZipWithSingleEntry(dex_file_, "assets/art-profile/baseline.prof", "valid_profile");
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(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=", FdHasContent("valid_profile"))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+                HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
+                      Return(ProfmanResult::kCopyAndUpdateSuccess)));
+
+  auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile());
+
+  EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::SUCCESS);
+  EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
+  std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath));
+  EXPECT_EQ(dst.profilePath.tmpPath, real_path);
+  CheckContent(real_path, "def");
+}
+
+// The input is a plain dex file.
+TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileNoProfilePlainDex) {
+  constexpr const char* kDexMagic = "dex\n";
+  CreateFile(dex_file_, kDexMagic + "dex_code"s);
+
+  auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile());
+
+  EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::NO_PROFILE);
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+// The input is neither a zip nor a plain dex file.
+TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileNotZipNotDex) {
+  CreateFile(dex_file_, "wrong_format");
+
+  auto [status, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile</*kExpectOk=*/false>());
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(status.getMessage(), HasSubstr("File is neither a zip file nor a plain dex file"));
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+// The input is a zip file without a profile entry.
+TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileNoProfileZipNoEntry) {
+  CreateZipWithSingleEntry(dex_file_, "classes.dex", "dex_code");
+
+  auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile());
+
+  EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::NO_PROFILE);
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+// The input is a zip file with a profile entry that doesn't match itself.
+TEST_F(ArtdTest, copyAndRewriteEmbeddedProfileBadProfileNoMatch) {
+  CreateZipWithSingleEntry(dex_file_, "assets/art-profile/baseline.prof", "no_match");
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch));
+
+  auto [result, dst] = OR_FAIL(RunCopyAndRewriteEmbeddedProfile());
+
+  EXPECT_EQ(result.status, CopyAndRewriteProfileResult::Status::BAD_PROFILE);
+  EXPECT_THAT(result.errorMsg, HasSubstr("The profile does not match the APK"));
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
 TEST_F(ArtdTest, commitTmpProfile) {
   std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path_));
   CreateFile(tmp_profile_file);
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 014f63e..bc543ef 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -60,6 +60,15 @@
             inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile);
 
     /**
+     * Similar to above. The difference is that the profile is not taken from a separate file but
+     * taken from `dexFile` itself. Specifically, if `dexFile` is a zip file, the profile is taken
+     * from `assets/art-profile/baseline.prof` in the zip. Returns `NO_PROFILE` if `dexFile` is not
+     * a zip file or it doesn't contain a profile.
+     */
+    com.android.server.art.CopyAndRewriteProfileResult copyAndRewriteEmbeddedProfile(
+            inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile);
+
+    /**
      * Moves the temporary profile to the permanent location.
      *
      * Throws fatal and non-fatal errors.