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