Merge "CtsInitTestCases: additional debug info in failure" into main
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 05143ed..c3dd92b 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -402,6 +402,8 @@
     return EXIT_SUCCESS;
 }
 
+}  // extern "C"
+
 int main(int argc, char** argv) {
 #if defined(STATIC_CRASHER)
     debuggerd_callbacks_t callbacks = {
@@ -427,5 +429,3 @@
 
     return usage();
 }
-
-};
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 0474ae7..11841b2 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -594,7 +594,7 @@
   if (tombstone.page_size() != 4096) {
     CBL("Page size: %d bytes", tombstone.page_size());
   } else if (tombstone.has_been_16kb_mode()) {
-    CBL("Has been in 16kb mode: yes");
+    CBL("Has been in 16 KB mode before: yes");
   }
 
   // Process header
diff --git a/fastboot/OWNERS b/fastboot/OWNERS
index 3dec07e..2444081 100644
--- a/fastboot/OWNERS
+++ b/fastboot/OWNERS
@@ -1,5 +1,6 @@
 dvander@google.com
 elsk@google.com
 enh@google.com
+sanglardf@google.com
 zhangkelvin@google.com
 
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 13af1e2..ccbb67e 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -36,9 +36,6 @@
   ],
   "kernel-presubmit": [
     {
-      "name": "adb-remount-sh"
-    },
-    {
       "name": "libdm_test"
     },
     {
diff --git a/fs_mgr/libsnapshot/scratch_super.cpp b/fs_mgr/libsnapshot/scratch_super.cpp
index 93c4bbd..2036905 100644
--- a/fs_mgr/libsnapshot/scratch_super.cpp
+++ b/fs_mgr/libsnapshot/scratch_super.cpp
@@ -25,6 +25,13 @@
 #include <sys/vfs.h>
 #include <unistd.h>
 
+#include <algorithm>
+#include <filesystem>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/macros.h>
@@ -41,12 +48,6 @@
 #include <fstab/fstab.h>
 #include <liblp/builder.h>
 #include <storage_literals/storage_literals.h>
-#include <algorithm>
-#include <filesystem>
-#include <memory>
-#include <optional>
-#include <string>
-#include <vector>
 
 #include "device_info.h"
 #include "scratch_super.h"
@@ -60,9 +61,18 @@
 namespace snapshot {
 
 static bool UmountScratch() {
-    auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
-    std::error_code ec;
+    Fstab fstab;
+    if (!ReadFstabFromProcMounts(&fstab)) {
+        LOG(ERROR) << "Cannot read /proc/mounts";
+        return false;
+    }
+    if (GetEntryForMountPoint(&fstab, kOtaMetadataMount) == nullptr) {
+        return true;
+    }
 
+    auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
+
+    std::error_code ec;
     if (std::filesystem::remove_all(ota_dir, ec) == static_cast<std::uintmax_t>(-1)) {
         LOG(ERROR) << "Failed to remove OTA directory: " << ec.message();
         return false;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 1a0d559..931de89 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -1345,6 +1345,7 @@
 
 TEST_F(SnapshotUpdateTest, SuperOtaMetadataTest) {
     auto info = new TestDeviceInfo(fake_super);
+    ASSERT_TRUE(CleanupScratchOtaMetadataIfPresent(info));
     ASSERT_TRUE(CreateScratchOtaMetadataOnSuper(info));
     std::string scratch_device = GetScratchOtaMetadataPartition();
     ASSERT_NE(scratch_device, "");
@@ -3071,6 +3072,18 @@
     ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
     gflags::ParseCommandLineFlags(&argc, &argv, false);
 
+    // During incremental flashing, snapshot updates are in progress.
+    //
+    // When snapshot update is in-progress, snapuserd daemon
+    // will be up and running. These tests will start and stop the daemon
+    // thereby interfering with the update and snapshot-merge progress.
+    // Hence, wait until the update is complete.
+    auto sm = android::snapshot::SnapshotManager::New();
+    while (sm->IsUserspaceSnapshotUpdateInProgress()) {
+        LOG(INFO) << "Snapshot update is in progress. Waiting...";
+        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+    }
+
     bool vab_legacy = false;
     if (FLAGS_force_mode == "vab-legacy") {
         vab_legacy = true;
diff --git a/init/README.md b/init/README.md
index 560c528..251fe98 100644
--- a/init/README.md
+++ b/init/README.md
@@ -369,6 +369,17 @@
 `setenv <name> <value>`
 > Set the environment variable _name_ to _value_ in the launched process.
 
+`shared_kallsyms`
+> If set, init will behave as if the service specified "file /proc/kallsyms r",
+  except the service will receive a duplicate of a single fd that init saved
+  during early second\_stage. This fd retains address visibility even after the
+  systemwide kptr\_restrict sysctl is set to its steady state on Android. The
+  ability to read from this fd is still constrained by selinux permissions,
+  which need to be granted separately and are gated by a neverallow.
+  Because of performance gotchas of concurrent use of this shared fd, all uses
+  need to coordinate via provisional flock(LOCK\_EX) locks on separately opened
+  /proc/kallsyms fds (since locking requires distinct open file descriptions).
+
 `shutdown <shutdown_behavior>`
 > Set shutdown behavior of the service process. When this is not specified,
   the service is killed during shutdown process by using SIGTERM and SIGKILL.
@@ -475,6 +486,12 @@
    2. Any time that property a transitions to value b, while property c already equals d.
    3. Any time that property c transitions to value d, while property a already equals b.
 
+Note that, for bootloader-provided properties (ro.boot.*), their action cannot be
+auto-triggered until `boot` stage. If they need to be triggered earlier, like at `early-boot`
+stage, they should be tied to the `event`. For example:
+
+`on early-boot && property:a=b`.
+
 
 Trigger Sequence
 ----------------
diff --git a/init/init.cpp b/init/init.cpp
index 5b0b0dd..b6ba6a8 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -1055,6 +1055,14 @@
         }
     }
 
+    // This needs to happen before SetKptrRestrictAction, as we are trying to
+    // open /proc/kallsyms while still being allowed to see the full addresses
+    // (since init holds CAP_SYSLOG, and Linux boots with kptr_restrict=0). The
+    // address visibility through the saved fd (more specifically, the backing
+    // open file description) will then be remembered by the kernel for the rest
+    // of its lifetime, even after we raise the kptr_restrict.
+    Service::OpenAndSaveStaticKallsymsFd();
+
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
diff --git a/init/property_service.cpp b/init/property_service.cpp
index f2606e3..31af94e 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -595,7 +595,7 @@
 }
 
 static void handle_property_set_fd(int fd) {
-    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
+    static constexpr uint32_t kDefaultSocketTimeout = 5000; /* ms */
 
     int s = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC);
     if (s == -1) {
diff --git a/init/service.cpp b/init/service.cpp
index d76a5d5..5630020 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -34,6 +34,7 @@
 #include <android-base/scopeguard.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <cutils/android_get_control_file.h>
 #include <cutils/sockets.h>
 #include <processgroup/processgroup.h>
 #include <selinux/selinux.h>
@@ -672,6 +673,14 @@
         }
     }
 
+    if (shared_kallsyms_file_) {
+        if (auto result = CreateSharedKallsymsFd(); result.ok()) {
+            descriptors.emplace_back(std::move(*result));
+        } else {
+            LOG(INFO) << "Could not obtain a copy of /proc/kallsyms: " << result.error();
+        }
+    }
+
     pid_t pid = -1;
     if (namespaces_.flags) {
         pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
@@ -835,6 +844,35 @@
     return unique_fd(signalfd(-1, &mask, SFD_CLOEXEC));
 }
 
+void Service::OpenAndSaveStaticKallsymsFd() {
+    Result<Descriptor> result = CreateSharedKallsymsFd();
+    if (!result.ok()) {
+      LOG(ERROR) << result.error();
+    }
+}
+
+// This function is designed to be called in two situations:
+// 1) early during second_stage init, to open and save the shared fd as a
+//    static (see OpenAndSaveStaticKallsymsFd).
+// 2) whenever a service requesting a copy of the fd is being started, at which
+//    point it will get a duplicated copy of the static fd.
+Result<Descriptor> Service::CreateSharedKallsymsFd() {
+    static constexpr char kallsyms_path[] = "/proc/kallsyms";
+    static int static_fd = open(kallsyms_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+    if (static_fd < 0) {
+        return ErrnoError() << "failed to open " << kallsyms_path;
+    }
+
+    unique_fd fd{fcntl(static_fd, F_DUPFD_CLOEXEC, /*min_fd=*/3)};
+    if (fd < 0) {
+        return ErrnoError() << "failed fcntl(F_DUPFD_CLOEXEC)";
+    }
+
+    // Use the same environment variable as if the service specified
+    // "file /proc/kallsyms r".
+    return Descriptor(std::string(ANDROID_FILE_ENV_PREFIX) + kallsyms_path, std::move(fd));
+}
+
 void Service::SetStartedInFirstStage(pid_t pid) {
     LOG(INFO) << "adding first-stage service '" << name_ << "'...";
 
diff --git a/init/service.h b/init/service.h
index ae75553..7193d7e 100644
--- a/init/service.h
+++ b/init/service.h
@@ -158,6 +158,7 @@
         static int sigchld_fd = CreateSigchldFd().release();
         return sigchld_fd;
     }
+    static void OpenAndSaveStaticKallsymsFd();
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
@@ -171,6 +172,7 @@
                     InterprocessFifo setsid_finished);
     void SetMountNamespace();
     static ::android::base::unique_fd CreateSigchldFd();
+    static Result<Descriptor> CreateSharedKallsymsFd();
 
     static unsigned long next_start_order_;
     static bool is_exec_service_running_;
@@ -188,6 +190,7 @@
     std::optional<std::string> fatal_reboot_target_;  // reboot target of fatal handler
     bool was_last_exit_ok_ =
             true;  // true if the service never exited, or exited with status code 0
+    bool shared_kallsyms_file_ = false; // pass the service a pre-opened fd to /proc/kallsyms
 
     std::optional<CapSet> capabilities_;
     ProcessAttributes proc_attr_;
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index ec3b176..4c31718 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -309,6 +309,11 @@
     return {};
 }
 
+Result<void> ServiceParser::ParseSharedKallsyms(std::vector<std::string>&& args) {
+    service_->shared_kallsyms_file_ = true;
+    return {};
+}
+
 Result<void> ServiceParser::ParseMemcgSwappiness(std::vector<std::string>&& args) {
     if (!ParseInt(args[1], &service_->swappiness_, 0)) {
         return Error() << "swappiness value must be equal or greater than 0";
@@ -603,6 +608,7 @@
         {"rlimit",                  {3,     3,    &ServiceParser::ParseProcessRlimit}},
         {"seclabel",                {1,     1,    &ServiceParser::ParseSeclabel}},
         {"setenv",                  {2,     2,    &ServiceParser::ParseSetenv}},
+        {"shared_kallsyms",         {0,     0,    &ServiceParser::ParseSharedKallsyms}},
         {"shutdown",                {1,     1,    &ServiceParser::ParseShutdown}},
         {"sigstop",                 {0,     0,    &ServiceParser::ParseSigstop}},
         {"socket",                  {3,     6,    &ServiceParser::ParseSocket}},
diff --git a/init/service_parser.h b/init/service_parser.h
index f06cfc4..e42b62b 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -67,6 +67,7 @@
     Result<void> ParseRestartPeriod(std::vector<std::string>&& args);
     Result<void> ParseSeclabel(std::vector<std::string>&& args);
     Result<void> ParseSetenv(std::vector<std::string>&& args);
+    Result<void> ParseSharedKallsyms(std::vector<std::string>&& args);
     Result<void> ParseShutdown(std::vector<std::string>&& args);
     Result<void> ParseSigstop(std::vector<std::string>&& args);
     Result<void> ParseSocket(std::vector<std::string>&& args);
diff --git a/janitors/OWNERS b/janitors/OWNERS
index c25d9e4..b317151 100644
--- a/janitors/OWNERS
+++ b/janitors/OWNERS
@@ -1,7 +1,19 @@
-# OWNERS file for projects that don't really have owners so much as volunteer janitors.
+# go/android-3p requires that all external projects have the "janitors" in
+# their OWNERS files.
+
+# These are also the "owners" for projects that don't really have owners
+# so much as volunteer janitors.
+
+# General maintenance.
+sadafebrahimi@google.com
+
+# C/C++.
 ccross@google.com
 cferris@google.com
-dwillemsen@google.com
 enh@google.com
+
+# Java.
 maco@google.com
-sadafebrahimi@google.com
+
+# Python.
+dwillemsen@google.com
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 1e76e76..6725acc 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -7,7 +7,6 @@
     module_type: "cc_defaults",
     config_namespace: "ANDROID",
     bool_variables: [
-        "memcg_v2_force_enabled",
         "cgroup_v2_sys_app_isolation",
     ],
     properties: [
@@ -19,11 +18,6 @@
     name: "libprocessgroup_build_flags_cc",
     cpp_std: "gnu++23",
     soong_config_variables: {
-        memcg_v2_force_enabled: {
-            cflags: [
-                "-DMEMCG_V2_FORCE_ENABLED=true",
-            ],
-        },
         cgroup_v2_sys_app_isolation: {
             cflags: [
                 "-DCGROUP_V2_SYS_APP_ISOLATION=true",
diff --git a/libprocessgroup/build_flags.h b/libprocessgroup/build_flags.h
index bc3e7df..d0948c3 100644
--- a/libprocessgroup/build_flags.h
+++ b/libprocessgroup/build_flags.h
@@ -16,20 +16,12 @@
 
 #pragma once
 
-#ifndef MEMCG_V2_FORCE_ENABLED
-#define MEMCG_V2_FORCE_ENABLED false
-#endif
-
 #ifndef CGROUP_V2_SYS_APP_ISOLATION
 #define CGROUP_V2_SYS_APP_ISOLATION false
 #endif
 
 namespace android::libprocessgroup_flags {
 
-inline consteval bool force_memcg_v2() {
-    return MEMCG_V2_FORCE_ENABLED;
-}
-
 inline consteval bool cgroup_v2_sys_app_isolation() {
     return CGROUP_V2_SYS_APP_ISOLATION;
 }
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 28b17c1..98179e8 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -32,6 +32,8 @@
 // Provides the path for an attribute in a specific process group
 // Returns false in case of error, true in case of success
 bool CgroupGetAttributePathForTask(const std::string& attr_name, pid_t tid, std::string* path);
+bool CgroupGetAttributePathForProcess(std::string_view attr_name, uid_t uid, pid_t pid,
+                                      std::string &path);
 
 bool SetTaskProfiles(pid_t tid, const std::vector<std::string>& profiles,
                      bool use_fd_cache = false);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 53168e3..a8fa50a 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -85,7 +85,8 @@
         CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg_kill);
         // cgroup.kill is not on the root cgroup, so check a non-root cgroup that should always
         // exist
-        cg_kill = ConvertUidToPath(cg_kill.c_str(), AID_ROOT) + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+        cg_kill = ConvertUidToPath(cg_kill.c_str(), AID_ROOT, true) + '/' +
+            PROCESSGROUP_CGROUP_KILL_FILE;
         cgroup_kill_available = access(cg_kill.c_str(), F_OK) == 0;
     });
 
@@ -154,6 +155,23 @@
     return true;
 }
 
+bool CgroupGetAttributePathForProcess(std::string_view attr_name, uid_t uid, pid_t pid,
+                                      std::string &path) {
+    const TaskProfiles& tp = TaskProfiles::GetInstance();
+    const IProfileAttribute* attr = tp.GetAttribute(attr_name);
+
+    if (attr == nullptr) {
+        return false;
+    }
+
+    if (!attr->GetPathForProcess(uid, pid, &path)) {
+        LOG(ERROR) << "Failed to find cgroup for uid " << uid << " pid " << pid;
+        return false;
+    }
+
+    return true;
+}
+
 bool UsePerAppMemcg() {
     bool low_ram_device = GetBoolProperty("ro.config.low_ram", false);
     return GetBoolProperty("ro.config.per_app_memcg", low_ram_device);
@@ -224,14 +242,14 @@
                                                        false);
 }
 
-static int RemoveCgroup(const char* cgroup, uid_t uid, pid_t pid) {
-    auto path = ConvertUidPidToPath(cgroup, uid, pid);
+static int RemoveCgroup(const char* cgroup, uid_t uid, pid_t pid, bool v2_path) {
+    auto path = ConvertUidPidToPath(cgroup, uid, pid, v2_path);
     int ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
 
     if (!ret && uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END) {
         // Isolated UIDs are unlikely to be reused soon after removal,
         // so free up the kernel resources for the UID level cgroup.
-        path = ConvertUidToPath(cgroup, uid);
+        path = ConvertUidToPath(cgroup, uid, v2_path);
         ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
     }
 
@@ -368,7 +386,7 @@
     if (CgroupsAvailable()) {
         std::string hierarchy_root_path, cgroup_v2_path;
         CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
-        cgroup_v2_path = ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
+        cgroup_v2_path = ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid, true);
 
         if (signal == SIGKILL && CgroupKillAvailable()) {
             LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_KILL_FILE << " to SIGKILL "
@@ -539,7 +557,7 @@
     CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
 
     const std::string cgroup_v2_path =
-            ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
+            ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid, true);
 
     const std::string eventsfile = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_EVENTS_FILE;
     android::base::unique_fd events_fd(open(eventsfile.c_str(), O_RDONLY));
@@ -605,7 +623,7 @@
                          << " after " << kill_duration.count() << " ms";
         }
 
-        ret = RemoveCgroup(hierarchy_root_path.c_str(), uid, initialPid);
+        ret = RemoveCgroup(hierarchy_root_path.c_str(), uid, initialPid, true);
         if (ret)
             PLOG(ERROR) << "Unable to remove cgroup " << cgroup_v2_path;
         else
@@ -616,9 +634,9 @@
             // memcg v2.
             std::string memcg_apps_path;
             if (CgroupGetMemcgAppsPath(&memcg_apps_path) &&
-                (ret = RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid)) < 0) {
+                (ret = RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid, false)) < 0) {
                 const auto memcg_v1_cgroup_path =
-                        ConvertUidPidToPath(memcg_apps_path.c_str(), uid, initialPid);
+                        ConvertUidPidToPath(memcg_apps_path.c_str(), uid, initialPid, false);
                 PLOG(ERROR) << "Unable to remove memcg v1 cgroup " << memcg_v1_cgroup_path;
             }
         }
@@ -640,7 +658,7 @@
 
 static int createProcessGroupInternal(uid_t uid, pid_t initialPid, std::string cgroup,
                                       bool activate_controllers) {
-    auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
+    auto uid_path = ConvertUidToPath(cgroup.c_str(), uid, activate_controllers);
 
     struct stat cgroup_stat;
     mode_t cgroup_mode = 0750;
@@ -667,7 +685,7 @@
         }
     }
 
-    auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid);
+    auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid, activate_controllers);
 
     if (!MkdirAndChown(uid_pid_path, cgroup_mode, cgroup_uid, cgroup_gid)) {
         PLOG(ERROR) << "Failed to make and chown " << uid_pid_path;
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 28902ef..720cb30 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -81,6 +81,11 @@
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
+    },
+    {
+      "Name": "CgroupProcs",
+      "Controller": "cgroup2",
+      "File": "cgroup.procs"
     }
   ],
 
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index c4e1fb6..0d1739e 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -27,9 +27,6 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <optional>
-
-#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <processgroup/cgroup_descriptor.h>
 #include <processgroup/processgroup.h>
@@ -260,39 +257,6 @@
     controller_.set_flags(flags);
 }
 
-static std::optional<bool> MGLRUDisabled() {
-    const std::string file_name = "/sys/kernel/mm/lru_gen/enabled";
-    std::string content;
-    if (!android::base::ReadFileToString(file_name, &content)) {
-        PLOG(ERROR) << "Failed to read MGLRU state from " << file_name;
-        return {};
-    }
-
-    return content == "0x0000";
-}
-
-static std::optional<bool> MEMCGDisabled(const CgroupDescriptorMap& descriptors) {
-    std::string cgroup_v2_root = CGROUP_V2_ROOT_DEFAULT;
-    const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
-    if (it == descriptors.end()) {
-        LOG(WARNING) << "No Cgroups2 path found in cgroups.json. Vendor has modified Android, and "
-                     << "kernel memory use will be higher than intended.";
-    } else if (it->second.controller()->path() != cgroup_v2_root) {
-        cgroup_v2_root = it->second.controller()->path();
-    }
-
-    const std::string file_name = cgroup_v2_root + "/cgroup.controllers";
-    std::string content;
-    if (!android::base::ReadFileToString(file_name, &content)) {
-        PLOG(ERROR) << "Failed to read cgroup controllers from " << file_name;
-        return {};
-    }
-
-    // If we've forced memcg to v2 and it's not available, then it could only have been disabled
-    // on the kernel command line (GKI sets CONFIG_MEMCG).
-    return content.find("memory") == std::string::npos;
-}
-
 static bool CreateV2SubHierarchy(const std::string& path, const CgroupDescriptorMap& descriptors) {
     const auto cgv2_iter = descriptors.find(CGROUPV2_HIERARCHY_NAME);
     if (cgv2_iter == descriptors.end()) return false;
@@ -335,17 +299,6 @@
         }
     }
 
-    if (android::libprocessgroup_flags::force_memcg_v2()) {
-        if (MGLRUDisabled().value_or(false)) {
-            LOG(WARNING) << "Memcg forced to v2 hierarchy with MGLRU disabled! "
-                         << "Global reclaim performance will suffer.";
-        }
-        if (MEMCGDisabled(descriptors).value_or(false)) {
-            LOG(WARNING) << "Memcg forced to v2 hierarchy while memcg is disabled by kernel "
-                         << "command line!";
-        }
-    }
-
     // System / app isolation.
     // This really belongs in early-init in init.rc, but we cannot use the flag there.
     if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation()) {
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index dc6c8c0..c03c257 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -150,8 +150,8 @@
     return uid < AID_APP_START;
 }
 
-std::string ConvertUidToPath(const char* root_cgroup_path, uid_t uid) {
-    if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation()) {
+std::string ConvertUidToPath(const char* root_cgroup_path, uid_t uid, bool v2_path) {
+    if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation() && v2_path) {
         if (isSystemApp(uid))
             return StringPrintf("%s/system/uid_%u", root_cgroup_path, uid);
         else
@@ -160,14 +160,14 @@
     return StringPrintf("%s/uid_%u", root_cgroup_path, uid);
 }
 
-std::string ConvertUidPidToPath(const char* root_cgroup_path, uid_t uid, pid_t pid) {
-    const std::string uid_path = ConvertUidToPath(root_cgroup_path, uid);
+std::string ConvertUidPidToPath(const char* root_cgroup_path, uid_t uid, pid_t pid, bool v2_path) {
+    const std::string uid_path = ConvertUidToPath(root_cgroup_path, uid, v2_path);
     return StringPrintf("%s/pid_%d", uid_path.c_str(), pid);
 }
 
 bool ProfileAttribute::GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const {
     if (controller()->version() == 2) {
-        const std::string cgroup_path = ConvertUidPidToPath(controller()->path(), uid, pid);
+        const std::string cgroup_path = ConvertUidPidToPath(controller()->path(), uid, pid, true);
         *path = cgroup_path + "/" + file_name();
         return true;
     }
@@ -199,7 +199,7 @@
         return true;
     }
 
-    const std::string cgroup_path = ConvertUidToPath(controller()->path(), uid);
+    const std::string cgroup_path = ConvertUidToPath(controller()->path(), uid, true);
     *path = cgroup_path + "/" + file_name();
     return true;
 }
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index d0b5043..b1d6115 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -258,5 +258,5 @@
     std::map<std::string, std::unique_ptr<IProfileAttribute>, std::less<>> attributes_;
 };
 
-std::string ConvertUidToPath(const char* root_cgroup_path, uid_t uid);
-std::string ConvertUidPidToPath(const char* root_cgroup_path, uid_t uid, pid_t pid);
+std::string ConvertUidToPath(const char* root_cgroup_path, uid_t uid, bool v2_path);
+std::string ConvertUidPidToPath(const char* root_cgroup_path, uid_t uid, pid_t pid, bool v2_path);
diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp
index 1c74d4e..266a53f 100644
--- a/libprocessgroup/util/Android.bp
+++ b/libprocessgroup/util/Android.bp
@@ -21,6 +21,7 @@
 
 cc_library_static {
     name: "libprocessgroup_util",
+    cpp_std: "gnu++23",
     vendor_available: true,
     product_available: true,
     ramdisk_available: true,
@@ -47,7 +48,6 @@
     static_libs: [
         "libjsoncpp",
     ],
-    defaults: ["libprocessgroup_build_flags_cc"],
 }
 
 cc_test {
diff --git a/libprocessgroup/util/util.cpp b/libprocessgroup/util/util.cpp
index 1401675..c772bc5 100644
--- a/libprocessgroup/util/util.cpp
+++ b/libprocessgroup/util/util.cpp
@@ -111,7 +111,6 @@
 }
 
 bool ReadDescriptorsFromFile(const std::string& file_name, CgroupDescriptorMap* descriptors) {
-    static constexpr bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
     std::vector<CgroupDescriptor> result;
     std::string json_doc;
 
@@ -133,14 +132,10 @@
         const Json::Value& cgroups = root["Cgroups"];
         for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
             std::string name = cgroups[i]["Controller"].asString();
-
-            if (force_memcg_v2 && name == "memory") continue;
-
             MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
         }
     }
 
-    bool memcgv2_present = false;
     std::string root_path;
     if (root.isMember("Cgroups2")) {
         const Json::Value& cgroups2 = root["Cgroups2"];
@@ -150,24 +145,10 @@
         const Json::Value& childGroups = cgroups2["Controllers"];
         for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
             std::string name = childGroups[i]["Controller"].asString();
-
-            if (force_memcg_v2 && name == "memory") memcgv2_present = true;
-
             MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
         }
     }
 
-    if (force_memcg_v2 && !memcgv2_present) {
-        LOG(INFO) << "Forcing memcg to v2 hierarchy";
-        Json::Value memcgv2;
-        memcgv2["Controller"] = "memory";
-        memcgv2["NeedsActivation"] = true;
-        memcgv2["Path"] = ".";
-        memcgv2["Optional"] = true;  // In case of cgroup_disabled=memory, so we can still boot
-        MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
-                                 root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
-    }
-
     return true;
 }
 
diff --git a/libprocessgroup/vts/Android.bp b/libprocessgroup/vts/Android.bp
new file mode 100644
index 0000000..1ec49a4
--- /dev/null
+++ b/libprocessgroup/vts/Android.bp
@@ -0,0 +1,15 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "vts_libprocessgroup",
+    srcs: ["vts_libprocessgroup.cpp"],
+    shared_libs: ["libbase"],
+    static_libs: ["libgmock"],
+    require_root: true,
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/libprocessgroup/vts/vts_libprocessgroup.cpp b/libprocessgroup/vts/vts_libprocessgroup.cpp
new file mode 100644
index 0000000..af0b0af
--- /dev/null
+++ b/libprocessgroup/vts/vts_libprocessgroup.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+#include <cerrno>
+#include <chrono>
+#include <cstdio>
+#include <filesystem>
+#include <future>
+#include <iostream>
+#include <optional>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+using android::base::ReadFileToString;
+using android::base::Split;
+using android::base::WriteStringToFile;
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+const std::string CGROUP_V2_ROOT_PATH = "/sys/fs/cgroup";
+
+std::optional<bool> isMemcgV2Enabled() {
+    if (std::string proc_cgroups; ReadFileToString("/proc/cgroups", &proc_cgroups)) {
+        const std::vector<std::string> lines = Split(proc_cgroups, "\n");
+        for (const std::string& line : lines) {
+            if (line.starts_with("memory")) {
+                const bool enabled = line.back() == '1';
+                if (!enabled) return false;
+
+                const std::vector<std::string> memcg_tokens = Split(line, "\t");
+                return memcg_tokens[1] == "0";  // 0 == default hierarchy == v2
+            }
+        }
+        // We know for sure it's not enabled, either because it is mounted as v1 (cgroups.json
+        // override) which would be detected above, or because it was intentionally disabled via
+        // kernel command line (cgroup_disable=memory), or because it's not built in to the kernel
+        // (CONFIG_MEMCG is not set).
+        return false;
+    }
+
+    // Problems accessing /proc/cgroups (sepolicy?) Try checking the root cgroup.controllers file.
+    perror("Warning: Could not read /proc/cgroups");
+    if (std::string controllers;
+        ReadFileToString(CGROUP_V2_ROOT_PATH + "/cgroup.controllers", &controllers)) {
+        return controllers.find("memory") != std::string::npos;
+    }
+
+    std::cerr << "Error: Could not read " << CGROUP_V2_ROOT_PATH
+              << "/cgroup.controllers: " << std::strerror(errno) << std::endl;
+    return std::nullopt;
+}
+
+std::optional<bool> checkRootSubtreeState() {
+    if (std::string controllers;
+        ReadFileToString(CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control", &controllers)) {
+        return controllers.find("memory") != std::string::npos;
+    }
+    std::cerr << "Error: Could not read " << CGROUP_V2_ROOT_PATH
+              << "/cgroup.subtree_control: " << std::strerror(errno) << std::endl;
+    return std::nullopt;
+}
+
+}  // anonymous namespace
+
+
+class MemcgV2Test : public testing::Test {
+  protected:
+    void SetUp() override {
+        std::optional<bool> memcgV2Enabled = isMemcgV2Enabled();
+        ASSERT_NE(memcgV2Enabled, std::nullopt);
+        if (!*memcgV2Enabled) GTEST_SKIP() << "Memcg v2 not enabled";
+    }
+};
+
+class MemcgV2SubdirTest : public testing::Test {
+  protected:
+    std::optional<std::string> mRandDir;
+
+    void SetUp() override {
+        std::optional<bool> memcgV2Enabled = isMemcgV2Enabled();
+        ASSERT_NE(memcgV2Enabled, std::nullopt);
+        if (!*memcgV2Enabled) GTEST_SKIP() << "Memcg v2 not enabled";
+
+        mRootSubtreeState = checkRootSubtreeState();
+        ASSERT_NE(mRootSubtreeState, std::nullopt);
+
+        if (!*mRootSubtreeState) {
+            ASSERT_TRUE(
+                    WriteStringToFile("+memory", CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control"))
+                    << "Could not enable memcg under root: " << std::strerror(errno);
+        }
+
+        // Make a new, temporary, randomly-named v2 cgroup in which we will attempt to activate
+        // memcg
+        std::random_device rd;
+        std::uniform_int_distribution dist(static_cast<int>('A'), static_cast<int>('Z'));
+        std::string randName = CGROUP_V2_ROOT_PATH + "/vts_libprocessgroup.";
+        for (int i = 0; i < 10; ++i) randName.append(1, static_cast<char>(dist(rd)));
+        ASSERT_TRUE(std::filesystem::create_directory(randName));
+        mRandDir = randName;  // For cleanup in TearDown
+
+        std::string subtree_controllers;
+        ASSERT_TRUE(ReadFileToString(*mRandDir + "/cgroup.controllers", &subtree_controllers));
+        ASSERT_NE(subtree_controllers.find("memory"), std::string::npos)
+                << "Memcg was not activated in child cgroup";
+    }
+
+    void TearDown() override {
+        if (mRandDir) {
+            if (!std::filesystem::remove(*mRandDir)) {
+                std::cerr << "Could not remove temporary memcg v2 test directory" << std::endl;
+            }
+        }
+
+        if (!*mRootSubtreeState) {
+            if (!WriteStringToFile("-memory", CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control")) {
+                std::cerr << "Could not disable memcg under root: " << std::strerror(errno)
+                          << std::endl;
+            }
+        }
+    }
+
+  private:
+    std::optional<bool> mRootSubtreeState;
+};
+
+
+TEST_F(MemcgV2SubdirTest, CanActivateMemcgV2Subtree) {
+    ASSERT_TRUE(WriteStringToFile("+memory", *mRandDir + "/cgroup.subtree_control"))
+            << "Could not enable memcg under child cgroup subtree";
+}
+
+// Test for fix: mm: memcg: use larger batches for proactive reclaim
+// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=287d5fedb377ddc232b216b882723305b27ae31a
+TEST_F(MemcgV2Test, ProactiveReclaimDoesntTakeForever) {
+    // Not all kernels have memory.reclaim
+    const std::filesystem::path reclaim(CGROUP_V2_ROOT_PATH + "/memory.reclaim");
+    if (!std::filesystem::exists(reclaim)) GTEST_SKIP() << "memory.reclaim not found";
+
+    // Use the total device memory as the amount to reclaim
+    const long numPages = sysconf(_SC_PHYS_PAGES);
+    const long pageSize = sysconf(_SC_PAGE_SIZE);
+    ASSERT_GT(numPages, 0);
+    ASSERT_GT(pageSize, 0);
+    const unsigned long long totalMem =
+            static_cast<unsigned long long>(numPages) * static_cast<unsigned long long>(pageSize);
+
+    auto fut = std::async(std::launch::async,
+                          [&]() { WriteStringToFile(std::to_string(totalMem), reclaim); });
+
+    // This is a test for completion within the timeout. The command is likely to "fail" since we
+    // are asking to reclaim all device memory.
+    ASSERT_NE(fut.wait_for(std::chrono::seconds(20)), std::future_status::timeout);
+}
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 883ed9c..f1670ae 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -569,6 +569,9 @@
     chown root log /proc/vmallocinfo
     chmod 0440 /proc/vmallocinfo
 
+    chown root log /proc/allocinfo
+    chmod 0440 /proc/allocinfo
+
     chown root log /proc/slabinfo
     chmod 0440 /proc/slabinfo