Add an artd method to delete runtime image files.

Bug: 299442352
Test: atest art_standalone_artd_tests
Change-Id: Ibc31bd5c0ad2d5d3ea1cfa427f76e1dc6fc93b11
Merged-In: Ibc31bd5c0ad2d5d3ea1cfa427f76e1dc6fc93b11
diff --git a/artd/artd.cc b/artd/artd.cc
index a4e0f32..89156c2 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -96,6 +96,7 @@
 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::RuntimeArtifactsPath;
 using ::aidl::com::android::server::art::VdexPath;
 using ::android::base::Dirname;
 using ::android::base::Error;
@@ -1154,6 +1155,16 @@
   return NonFatal(ART_FORMAT("Fstab entries not found for '{}'", in_dexFile));
 }
 
+ScopedAStatus Artd::deleteRuntimeArtifacts(const RuntimeArtifactsPath& in_runtimeArtifactsPath,
+                                           int64_t* _aidl_return) {
+  OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(in_runtimeArtifactsPath));
+  for (const std::string& file :
+       OR_RETURN_NON_FATAL(ListRuntimeArtifactsFiles(in_runtimeArtifactsPath))) {
+    *_aidl_return += GetSizeAndDeleteFile(file);
+  }
+  return ScopedAStatus::ok();
+}
+
 ScopedAStatus Artd::validateDexPath(const std::string& in_dexPath,
                                     std::optional<std::string>* _aidl_return) {
   if (Result<void> result = ValidateDexPath(in_dexPath); !result.ok()) {
diff --git a/artd/artd.h b/artd/artd.h
index 774f11a..c6bce72 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -166,6 +166,10 @@
 
   ndk::ScopedAStatus isInDalvikCache(const std::string& in_dexFile, bool* _aidl_return) override;
 
+  ndk::ScopedAStatus deleteRuntimeArtifacts(
+      const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath,
+      int64_t* _aidl_return) override;
+
   ndk::ScopedAStatus validateDexPath(const std::string& in_dexPath,
                                      std::optional<std::string>* _aidl_return) override;
 
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index ae18b1a..41c1755 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -81,6 +81,7 @@
 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::RuntimeArtifactsPath;
 using ::aidl::com::android::server::art::VdexPath;
 using ::android::base::Append;
 using ::android::base::Error;
@@ -2118,6 +2119,108 @@
   EXPECT_THAT(is_in_dalvik_cache("/foo"), HasValue(true));
 }
 
+TEST_F(ArtdTest, deleteRuntimeArtifacts) {
+  // TODO(b/289037540): Fix this.
+  if (getuid() != kRootUid) {
+    GTEST_SKIP() << "This test requires root access";
+  }
+
+  std::vector<std::string> removed_files;
+  std::vector<std::string> kept_files;
+
+  auto CreateRemovedFile = [&](const std::string& path) {
+    CreateFile(path);
+    removed_files.push_back(path);
+  };
+
+  auto CreateKeptFile = [&](const std::string& path) {
+    CreateFile(path);
+    kept_files.push_back(path);
+  };
+
+  CreateKeptFile(android_data_ +
+                 "/user/0/com.android.different_package/cache/oat_primary/arm64/base.art");
+  CreateKeptFile(android_data_ +
+                 "/user/0/com.android.foo/cache/oat_primary/arm64/different_dex.art");
+  CreateKeptFile(android_data_ +
+                 "/user/0/com.android.foo/cache/oat_primary/different_isa/base.art");
+  CreateKeptFile(android_data_ +
+                 "/user/0/com.android.foo/cache/not_oat_dir/oat_primary/arm64/base.art");
+
+  CreateRemovedFile(android_data_ + "/user_de/0/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateRemovedFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateRemovedFile(android_data_ + "/user/1/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateRemovedFile(android_expand_ +
+                    "/123456-7890/user/1/com.android.foo/cache/oat_primary/arm64/base.art");
+
+  int64_t aidl_return;
+  ASSERT_TRUE(
+      artd_
+          ->deleteRuntimeArtifacts(
+              {.packageName = "com.android.foo", .dexPath = "/a/b/base.apk", .isa = "arm64"},
+              &aidl_return)
+          .isOk());
+
+  for (const std::string& path : removed_files) {
+    EXPECT_FALSE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be removed", path);
+  }
+
+  for (const std::string& path : kept_files) {
+    EXPECT_TRUE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be kept", path);
+  }
+}
+
+TEST_F(ArtdTest, deleteRuntimeArtifactsSpecialChars) {
+  // TODO(b/289037540): Fix this.
+  if (getuid() != kRootUid) {
+    GTEST_SKIP() << "This test requires root access";
+  }
+
+  std::vector<std::string> removed_files;
+  std::vector<std::string> kept_files;
+
+  auto CreateRemovedFile = [&](const std::string& path) {
+    CreateFile(path);
+    removed_files.push_back(path);
+  };
+
+  auto CreateKeptFile = [&](const std::string& path) {
+    CreateFile(path);
+    kept_files.push_back(path);
+  };
+
+  CreateKeptFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/base.art");
+
+  CreateRemovedFile(android_data_ + "/user/0/*/cache/oat_primary/arm64/base.art");
+  CreateRemovedFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/*/base.art");
+  CreateRemovedFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/*.art");
+
+  int64_t aidl_return;
+  ASSERT_TRUE(
+      artd_
+          ->deleteRuntimeArtifacts({.packageName = "*", .dexPath = "/a/b/base.apk", .isa = "arm64"},
+                                   &aidl_return)
+          .isOk());
+  ASSERT_TRUE(artd_
+                  ->deleteRuntimeArtifacts(
+                      {.packageName = "com.android.foo", .dexPath = "/a/b/*.apk", .isa = "arm64"},
+                      &aidl_return)
+                  .isOk());
+  ASSERT_TRUE(artd_
+                  ->deleteRuntimeArtifacts(
+                      {.packageName = "com.android.foo", .dexPath = "/a/b/base.apk", .isa = "*"},
+                      &aidl_return)
+                  .isOk());
+
+  for (const std::string& path : removed_files) {
+    EXPECT_FALSE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be removed", path);
+  }
+
+  for (const std::string& path : kept_files) {
+    EXPECT_TRUE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be kept", path);
+  }
+}
+
 }  // namespace
 }  // namespace artd
 }  // namespace art
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 3b55297..f7b2251 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -22,7 +22,10 @@
     boolean isAlive();
 
     /**
-     * Deletes artifacts and returns the released space, in bytes.
+     * Deletes dexopt artifacts and returns the released space, in bytes.
+     *
+     * Note that this method doesn't delete runtime artifacts. To delete them, call
+     * `deleteRuntimeArtifacts`.
      *
      * Throws fatal errors. Logs and ignores non-fatal errors.
      */
@@ -180,6 +183,14 @@
     boolean isInDalvikCache(@utf8InCpp String dexFile);
 
     /**
+     * Deletes runtime artifacts and returns the released space, in bytes.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long deleteRuntimeArtifacts(
+            in com.android.server.art.RuntimeArtifactsPath runtimeArtifactsPath);
+
+    /**
      * Returns an error message if the given dex path is invalid, or null if the validation
      * passes.
      */
diff --git a/artd/binder/com/android/server/art/RuntimeArtifactsPath.aidl b/artd/binder/com/android/server/art/RuntimeArtifactsPath.aidl
new file mode 100644
index 0000000..4096ea9
--- /dev/null
+++ b/artd/binder/com/android/server/art/RuntimeArtifactsPath.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to the runtime artifacts of a dex file (i.e., ART files generated by the
+ * runtime, not by dexopt).
+ *
+ * @hide
+ */
+parcelable RuntimeArtifactsPath {
+    /** The name of the package. */
+    @utf8InCpp String packageName;
+    /** The absolute path starting with '/' to the dex file (i.e., APK or JAR file). */
+    @utf8InCpp String dexPath;
+    /** The instruction set of the dexopt artifacts. */
+    @utf8InCpp String isa;
+}
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
index 6ff9b95..0a77d9d 100644
--- a/artd/path_utils.cc
+++ b/artd/path_utils.cc
@@ -30,6 +30,7 @@
 #include "file_utils.h"
 #include "fstab/fstab.h"
 #include "oat_file_assistant.h"
+#include "runtime_image.h"
 #include "tools/tools.h"
 
 namespace art {
@@ -40,6 +41,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::RuntimeArtifactsPath;
 using ::aidl::com::android::server::art::VdexPath;
 using ::android::base::Error;
 using ::android::base::Result;
@@ -155,6 +157,35 @@
   return tools::Glob(patterns);
 }
 
+Result<std::vector<std::string>> ListRuntimeArtifactsFiles(
+    const RuntimeArtifactsPath& runtime_artifacts_path) {
+  std::string android_data = OR_RETURN(GetAndroidDataOrError());
+  std::string android_expand = OR_RETURN(GetAndroidExpandOrError());
+
+  // See `art::tools::Glob` for the syntax.
+  std::vector<std::string> patterns;
+
+  for (const std::string& data_root : {android_data, android_expand + "/*"}) {
+    for (const char* user_dir : {"/user", "/user_de"}) {
+      std::string data_dir =
+          data_root + user_dir + "/*/" + tools::EscapeGlob(runtime_artifacts_path.packageName);
+      patterns.push_back(
+          RuntimeImage::GetRuntimeImagePath(data_dir,
+                                            tools::EscapeGlob(runtime_artifacts_path.dexPath),
+                                            tools::EscapeGlob(runtime_artifacts_path.isa)));
+    }
+  }
+
+  return tools::Glob(patterns);
+}
+
+Result<void> ValidateRuntimeArtifactsPath(const RuntimeArtifactsPath& runtime_artifacts_path) {
+  OR_RETURN(ValidatePathElement(runtime_artifacts_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElement(runtime_artifacts_path.isa, "isa"));
+  OR_RETURN(ValidateDexPath(runtime_artifacts_path.dexPath));
+  return {};
+}
+
 Result<void> ValidateDexPath(const std::string& dex_path) {
   OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
   return {};
diff --git a/artd/path_utils.h b/artd/path_utils.h
index a126118..48640d0 100644
--- a/artd/path_utils.h
+++ b/artd/path_utils.h
@@ -31,6 +31,12 @@
 // Returns all existing files that are managed by artd.
 android::base::Result<std::vector<std::string>> ListManagedFiles();
 
+android::base::Result<std::vector<std::string>> ListRuntimeArtifactsFiles(
+    const aidl::com::android::server::art::RuntimeArtifactsPath& runtime_artifacts_path);
+
+android::base::Result<void> ValidateRuntimeArtifactsPath(
+    const aidl::com::android::server::art::RuntimeArtifactsPath& runtime_artifacts_path);
+
 android::base::Result<void> ValidateDexPath(const std::string& dex_path);
 
 android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name);
diff --git a/libarttools/tools/tools.cc b/libarttools/tools/tools.cc
index 4ec9d9a..3a59321 100644
--- a/libarttools/tools/tools.cc
+++ b/libarttools/tools/tools.cc
@@ -22,6 +22,7 @@
 #include <algorithm>
 #include <filesystem>
 #include <functional>
+#include <regex>
 #include <string>
 #include <string_view>
 #include <system_error>
@@ -138,5 +139,9 @@
   return results;
 }
 
+std::string EscapeGlob(const std::string& str) {
+  return std::regex_replace(str, std::regex(R"re(\*|\?|\[)re"), "[$&]");
+}
+
 }  // namespace tools
 }  // namespace art
diff --git a/libarttools/tools/tools.h b/libarttools/tools/tools.h
index c2bcee7..142d19b 100644
--- a/libarttools/tools/tools.h
+++ b/libarttools/tools/tools.h
@@ -37,6 +37,9 @@
 std::vector<std::string> Glob(const std::vector<std::string>& patterns,
                               std::string_view root_dir = "/");
 
+// Escapes a string so that it's not recognized as a wildcard pattern for `Glob`.
+std::string EscapeGlob(const std::string& str);
+
 }  // namespace tools
 }  // namespace art
 
diff --git a/libarttools/tools/tools_test.cc b/libarttools/tools/tools_test.cc
index 2f61181..09ec35f 100644
--- a/libarttools/tools/tools_test.cc
+++ b/libarttools/tools/tools_test.cc
@@ -110,6 +110,37 @@
                                    scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt"));
 }
 
+TEST_F(ArtToolsTest, EscapeGlob) {
+  CreateFile(scratch_path_ + "/**");
+  CreateFile(scratch_path_ + "/*.txt");
+  CreateFile(scratch_path_ + "/?.txt");
+  CreateFile(scratch_path_ + "/[a-z].txt");
+  CreateFile(scratch_path_ + "/**.txt");
+  CreateFile(scratch_path_ + "/??.txt");
+  CreateFile(scratch_path_ + "/[a-z[a-z]][a-z].txt");
+
+  // Paths that shouldn't be matched if the paths above are escaped.
+  CreateFile(scratch_path_ + "/abc/b.txt");
+  CreateFile(scratch_path_ + "/b.txt");
+  CreateFile(scratch_path_ + "/*b.txt");
+  CreateFile(scratch_path_ + "/?b.txt");
+  CreateFile(scratch_path_ + "/[a-zb]b.txt");
+
+  // Verifies that the escaped path only matches the given path.
+  auto verify_escape = [this](const std::string& file) {
+    EXPECT_THAT(Glob({EscapeGlob(file)}, scratch_path_), UnorderedElementsAre(file));
+  };
+
+  verify_escape(scratch_path_ + "/**");
+  verify_escape(scratch_path_ + "/*.txt");
+  verify_escape(scratch_path_ + "/?.txt");
+  verify_escape(scratch_path_ + "/[a-z].txt");
+  verify_escape(scratch_path_ + "/**.txt");
+  verify_escape(scratch_path_ + "/**");
+  verify_escape(scratch_path_ + "/??.txt");
+  verify_escape(scratch_path_ + "/[a-z[a-z]][a-z].txt");
+}
+
 }  // namespace
 }  // namespace tools
 }  // namespace art
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
index 34a9c08..23b7354 100644
--- a/runtime/runtime_image.cc
+++ b/runtime/runtime_image.cc
@@ -17,13 +17,12 @@
 #include "runtime_image.h"
 
 #include <lz4.h>
-#include <sstream>
 #include <unistd.h>
 
 #include "android-base/file.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
-
+#include "arch/instruction_set.h"
 #include "base/arena_allocator.h"
 #include "base/arena_containers.h"
 #include "base/bit_utils.h"
@@ -1813,21 +1812,28 @@
   friend class NativePointerVisitor;
 };
 
-static std::string GetOatPath() {
-  const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory();
-  if (data_dir.empty()) {
-    // The data ditectory is empty for tests.
+static std::string GetRuntimeImageDir(const std::string& app_data_dir) {
+  if (app_data_dir.empty()) {
+    // The data directory is empty for tests.
     return "";
   }
-  return data_dir + "/cache/oat_primary/";
+  return app_data_dir + "/cache/oat_primary/";
 }
 
 // Note: this may return a relative path for tests.
-std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) {
+std::string RuntimeImage::GetRuntimeImagePath(const std::string& app_data_dir,
+                                              const std::string& dex_location,
+                                              const std::string& isa) {
   std::string basename = android::base::Basename(dex_location);
   std::string filename = ReplaceFileExtension(basename, "art");
 
-  return GetOatPath() + GetInstructionSetString(kRuntimeISA) + "/" + filename;
+  return GetRuntimeImageDir(app_data_dir) + isa + "/" + filename;
+}
+
+std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) {
+  return GetRuntimeImagePath(Runtime::Current()->GetProcessDataDirectory(),
+                             dex_location,
+                             GetInstructionSetString(kRuntimeISA));
 }
 
 static bool EnsureDirectoryExists(const std::string& directory, std::string* error_msg) {
@@ -1848,7 +1854,7 @@
     *error_msg = "Cannot generate an app image without a boot image";
     return false;
   }
-  std::string oat_path = GetOatPath();
+  std::string oat_path = GetRuntimeImageDir(Runtime::Current()->GetProcessDataDirectory());
   if (!oat_path.empty() && !EnsureDirectoryExists(oat_path, error_msg)) {
     return false;
   }
diff --git a/runtime/runtime_image.h b/runtime/runtime_image.h
index d494e1c..ed891b4 100644
--- a/runtime/runtime_image.h
+++ b/runtime/runtime_image.h
@@ -27,6 +27,14 @@
   static bool WriteImageToDisk(std::string* error_msg);
 
   // Gets the path where a runtime-generated app image is stored.
+  //
+  // If any of the arguments is a valid glob (a pattern that contains '**' or those documented in
+  // glob(7)), returns a valid glob.
+  static std::string GetRuntimeImagePath(const std::string& app_data_dir,
+                                         const std::string& dex_location,
+                                         const std::string& isa);
+
+  // Same as above, but takes data dir and ISA from the runtime.
   static std::string GetRuntimeImagePath(const std::string& dex_location);
 };