libprocessgroup: Remove cgroup.rc file

The cgroup.rc file was introduced in 192aee782 ("libprocessgroup: Add
support for task profiles") back with the initial support for task
profiles. It was intended to optimize performance associated with cgroup
operations. However over time, supporting this file led to making
libprocessgroup code more complicated (such as the cgrouprc LLNDK
interface), and the file ended up getting mmaped into nearly every
process on Android even though only a handful of them actually use it.
Replacing this file with reading and parsing of cgroup information on
demand allows us to simplify and shrink libprocessgroup, and eliminates
thousands of unused mappings without negatively affecting boot time or
other performance metrics.

Bug: 349105928
Test: Verified with memcg v2 and MaxActivationDepth 1 on Cuttlefish, Raven, and Mokey
Change-Id: Ic3f01fdf7fda89a56ab80657e1cf4573156273e6
diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp
index 54ba69b..1c74d4e 100644
--- a/libprocessgroup/util/Android.bp
+++ b/libprocessgroup/util/Android.bp
@@ -37,8 +37,16 @@
         "include",
     ],
     srcs: [
+        "cgroup_controller.cpp",
+        "cgroup_descriptor.cpp",
         "util.cpp",
     ],
+    shared_libs: [
+        "libbase",
+    ],
+    static_libs: [
+        "libjsoncpp",
+    ],
     defaults: ["libprocessgroup_build_flags_cc"],
 }
 
diff --git a/libprocessgroup/util/cgroup_controller.cpp b/libprocessgroup/util/cgroup_controller.cpp
new file mode 100644
index 0000000..fb41680
--- /dev/null
+++ b/libprocessgroup/util/cgroup_controller.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 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 <processgroup/cgroup_controller.h>
+
+#include <cstring>
+
+CgroupController::CgroupController(uint32_t version, uint32_t flags, const std::string& name,
+                                   const std::string& path, uint32_t max_activation_depth)
+    : version_(version), flags_(flags), max_activation_depth_(max_activation_depth) {
+    // strlcpy isn't available on host. Although there is an implementation
+    // in licutils, libcutils itself depends on libcgrouprc_format, causing
+    // a circular dependency.
+    strncpy(name_, name.c_str(), sizeof(name_) - 1);
+    name_[sizeof(name_) - 1] = '\0';
+    strncpy(path_, path.c_str(), sizeof(path_) - 1);
+    path_[sizeof(path_) - 1] = '\0';
+}
+
+uint32_t CgroupController::version() const {
+    return version_;
+}
+
+uint32_t CgroupController::flags() const {
+    return flags_;
+}
+
+uint32_t CgroupController::max_activation_depth() const {
+    return max_activation_depth_;
+}
+
+const char* CgroupController::name() const {
+    return name_;
+}
+
+const char* CgroupController::path() const {
+    return path_;
+}
+
+void CgroupController::set_flags(uint32_t flags) {
+    flags_ = flags;
+}
\ No newline at end of file
diff --git a/libprocessgroup/util/cgroup_descriptor.cpp b/libprocessgroup/util/cgroup_descriptor.cpp
new file mode 100644
index 0000000..4d3347f
--- /dev/null
+++ b/libprocessgroup/util/cgroup_descriptor.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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 <processgroup/cgroup_descriptor.h>
+
+#include <processgroup/util.h>  // For flag values
+
+CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
+                                   const std::string& path, mode_t mode, const std::string& uid,
+                                   const std::string& gid, uint32_t flags,
+                                   uint32_t max_activation_depth)
+    : controller_(version, flags, name, path, max_activation_depth),
+      mode_(mode),
+      uid_(uid),
+      gid_(gid) {}
+
+void CgroupDescriptor::set_mounted(bool mounted) {
+    uint32_t flags = controller_.flags();
+    if (mounted) {
+        flags |= CGROUPRC_CONTROLLER_FLAG_MOUNTED;
+    } else {
+        flags &= ~CGROUPRC_CONTROLLER_FLAG_MOUNTED;
+    }
+    controller_.set_flags(flags);
+}
diff --git a/libprocessgroup/util/include/processgroup/cgroup_controller.h b/libprocessgroup/util/include/processgroup/cgroup_controller.h
new file mode 100644
index 0000000..fe6a829
--- /dev/null
+++ b/libprocessgroup/util/include/processgroup/cgroup_controller.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+// Minimal controller description
+struct CgroupController {
+  public:
+    CgroupController() = default;
+    CgroupController(uint32_t version, uint32_t flags, const std::string& name,
+                     const std::string& path, uint32_t max_activation_depth);
+
+    uint32_t version() const;
+    uint32_t flags() const;
+    uint32_t max_activation_depth() const;
+    const char* name() const;
+    const char* path() const;
+
+    void set_flags(uint32_t flags);
+
+  private:
+    static constexpr size_t CGROUP_NAME_BUF_SZ = 16;
+    static constexpr size_t CGROUP_PATH_BUF_SZ = 32;
+
+    uint32_t version_ = 0;
+    uint32_t flags_ = 0;
+    uint32_t max_activation_depth_ = UINT32_MAX;
+    char name_[CGROUP_NAME_BUF_SZ] = {};
+    char path_[CGROUP_PATH_BUF_SZ] = {};
+};
\ No newline at end of file
diff --git a/libprocessgroup/util/include/processgroup/cgroup_descriptor.h b/libprocessgroup/util/include/processgroup/cgroup_descriptor.h
new file mode 100644
index 0000000..1afd2ee
--- /dev/null
+++ b/libprocessgroup/util/include/processgroup/cgroup_descriptor.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+#include <sys/stat.h>
+
+#include <processgroup/cgroup_controller.h>
+
+// Complete controller description for mounting cgroups
+class CgroupDescriptor {
+  public:
+    CgroupDescriptor(uint32_t version, const std::string& name, const std::string& path,
+                     mode_t mode, const std::string& uid, const std::string& gid, uint32_t flags,
+                     uint32_t max_activation_depth);
+
+    const CgroupController* controller() const { return &controller_; }
+    mode_t mode() const { return mode_; }
+    std::string uid() const { return uid_; }
+    std::string gid() const { return gid_; }
+
+    void set_mounted(bool mounted);
+
+  private:
+    CgroupController controller_;
+    mode_t mode_ = 0;
+    std::string uid_;
+    std::string gid_;
+};
diff --git a/libprocessgroup/util/include/processgroup/util.h b/libprocessgroup/util/include/processgroup/util.h
index 8d013af..d592a63 100644
--- a/libprocessgroup/util/include/processgroup/util.h
+++ b/libprocessgroup/util/include/processgroup/util.h
@@ -16,10 +16,18 @@
 
 #pragma once
 
+#include <map>
 #include <string>
 
-namespace util {
+#include "cgroup_descriptor.h"
+
+// Duplicated from cgrouprc.h. Don't depend on libcgrouprc here.
+#define CGROUPRC_CONTROLLER_FLAG_MOUNTED 0x1
+#define CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION 0x2
+#define CGROUPRC_CONTROLLER_FLAG_OPTIONAL 0x4
 
 unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path);
 
-}  // namespace util
+using CgroupControllerName = std::string;
+using CgroupDescriptorMap = std::map<CgroupControllerName, CgroupDescriptor>;
+bool ReadDescriptors(CgroupDescriptorMap* descriptors);
diff --git a/libprocessgroup/util/tests/util.cpp b/libprocessgroup/util/tests/util.cpp
index 1de7d6f..6caef8e 100644
--- a/libprocessgroup/util/tests/util.cpp
+++ b/libprocessgroup/util/tests/util.cpp
@@ -18,8 +18,6 @@
 
 #include "gtest/gtest.h"
 
-using util::GetCgroupDepth;
-
 TEST(EmptyInputs, bothEmpty) {
     EXPECT_EQ(GetCgroupDepth({}, {}), 0);
 }
diff --git a/libprocessgroup/util/util.cpp b/libprocessgroup/util/util.cpp
index 9b88a22..bff4c6f 100644
--- a/libprocessgroup/util/util.cpp
+++ b/libprocessgroup/util/util.cpp
@@ -18,9 +18,33 @@
 
 #include <algorithm>
 #include <iterator>
+#include <optional>
+#include <string_view>
+
+#include <mntent.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <json/reader.h>
+#include <json/value.h>
+
+#include "../build_flags.h"
+#include "../internal.h"
+
+using android::base::GetUintProperty;
 
 namespace {
 
+constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
+constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
+constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
+
+// This should match the publicly declared value in processgroup.h,
+// but we don't want this library to depend on libprocessgroup.
+constexpr std::string CGROUPV2_HIERARCHY_NAME_INTERNAL = "cgroup2";
+
 const char SEP = '/';
 
 std::string DeduplicateAndTrimSeparators(const std::string& path) {
@@ -42,9 +66,135 @@
     return ret;
 }
 
+void MergeCgroupToDescriptors(CgroupDescriptorMap* descriptors, const Json::Value& cgroup,
+                              const std::string& name, const std::string& root_path,
+                              int cgroups_version) {
+    const std::string cgroup_path = cgroup["Path"].asString();
+    std::string path;
+
+    if (!root_path.empty()) {
+        path = root_path;
+        if (cgroup_path != ".") {
+            path += "/";
+            path += cgroup_path;
+        }
+    } else {
+        path = cgroup_path;
+    }
+
+    uint32_t controller_flags = 0;
+
+    if (cgroup["NeedsActivation"].isBool() && cgroup["NeedsActivation"].asBool()) {
+        controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
+    }
+
+    if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
+        controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+    }
+
+    uint32_t max_activation_depth = UINT32_MAX;
+    if (cgroup.isMember("MaxActivationDepth")) {
+        max_activation_depth = cgroup["MaxActivationDepth"].asUInt();
+    }
+
+    CgroupDescriptor descriptor(
+            cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
+            cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags,
+            max_activation_depth);
+
+    auto iter = descriptors->find(name);
+    if (iter == descriptors->end()) {
+        descriptors->emplace(name, descriptor);
+    } else {
+        iter->second = descriptor;
+    }
+}
+
+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;
+
+    if (!android::base::ReadFileToString(file_name, &json_doc)) {
+        PLOG(ERROR) << "Failed to read task profiles from " << file_name;
+        return false;
+    }
+
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+    Json::Value root;
+    std::string errorMessage;
+    if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
+        LOG(ERROR) << "Failed to parse cgroups description: " << errorMessage;
+        return false;
+    }
+
+    if (root.isMember("Cgroups")) {
+        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"];
+        root_path = cgroups2["Path"].asString();
+        MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME_INTERNAL, "", 2);
+
+        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;
+}
+
+using MountDir = std::string;
+using MountOpts = std::string;
+static std::optional<std::map<MountDir, MountOpts>> ReadCgroupV1Mounts() {
+    FILE* fp = setmntent("/proc/mounts", "r");
+    if (fp == nullptr) {
+        PLOG(ERROR) << "Failed to read mounts";
+        return std::nullopt;
+    }
+
+    std::map<MountDir, MountOpts> mounts;
+    const std::string_view CGROUP_V1_TYPE = "cgroup";
+    for (mntent* mentry = getmntent(fp); mentry != nullptr; mentry = getmntent(fp)) {
+        if (mentry->mnt_type && CGROUP_V1_TYPE == mentry->mnt_type &&
+            mentry->mnt_dir && mentry->mnt_opts) {
+            mounts[mentry->mnt_dir] = mentry->mnt_opts;
+        }
+    }
+    endmntent(fp);
+
+    return mounts;
+}
+
 }  // anonymous namespace
 
-namespace util {
 
 unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) {
     const std::string deduped_root = DeduplicateAndTrimSeparators(controller_root);
@@ -56,4 +206,47 @@
     return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(), SEP);
 }
 
-}  // namespace util
+bool ReadDescriptors(CgroupDescriptorMap* descriptors) {
+    // load system cgroup descriptors
+    if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
+        return false;
+    }
+
+    // load API-level specific system cgroups descriptors if available
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
+    if (api_level > 0) {
+        std::string api_cgroups_path =
+                android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
+        if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
+            if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
+                return false;
+            }
+        }
+    }
+
+    // load vendor cgroup descriptors if the file exists
+    if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
+        !ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
+        return false;
+    }
+
+    // check for v1 mount/usability status
+    std::optional<std::map<MountDir, MountOpts>> v1Mounts;
+    for (auto& [name, descriptor] : *descriptors) {
+        const CgroupController* const controller = descriptor.controller();
+
+        if (controller->version() != 1) continue;
+
+        // Read only once, and only if we have at least one v1 controller
+        if (!v1Mounts) {
+            v1Mounts = ReadCgroupV1Mounts();
+            if (!v1Mounts) return false;
+        }
+
+        if (const auto it = v1Mounts->find(controller->path()); it != v1Mounts->end()) {
+            if (it->second.contains(controller->name())) descriptor.set_mounted(true);
+        }
+    }
+
+    return true;
+}