libprocessgroup: Move CgroupSetupCgroups() to libprocessgroup_setup

Only init uses SetupCgroups. This functionality is
moved from libprocessgroup to its own library, and only
init links to it.

Also, merge CgroupSetupCgroups() with CgroupMap::SetupCgroups()
because the former is just an alias of the latter, and
CgroupMap does not belong to libcgrouprc_setup.

Test: boots
Bug: 123664216
Change-Id: I941dc0c415e2b22ae663d43e30dc7a464687325e
diff --git a/init/Android.bp b/init/Android.bp
index 8292aa0..e834e99 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -82,6 +82,7 @@
         "liblogwrap",
         "liblp",
         "libprocessgroup",
+        "libprocessgroup_setup",
         "libselinux",
         "libutils",
     ],
diff --git a/init/init.cpp b/init/init.cpp
index cdec41c..1792f28 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -45,6 +45,7 @@
 #include <libavb/libavb.h>
 #include <libgsi/libgsi.h>
 #include <processgroup/processgroup.h>
+#include <processgroup/setup.h>
 #include <selinux/android.h>
 
 #ifndef RECOVERY
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 1e66fa4..3b852ae 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -44,222 +44,10 @@
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
-static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
-static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
-
 static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
 static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
 static constexpr const char* CGROUP_TASKS_FILE_V2 = "/cgroup.tasks";
 
-static bool Mkdir(const std::string& path, mode_t mode, const std::string& uid,
-                  const std::string& gid) {
-    if (mode == 0) {
-        mode = 0755;
-    }
-
-    if (mkdir(path.c_str(), mode) != 0) {
-        /* chmod in case the directory already exists */
-        if (errno == EEXIST) {
-            if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
-                // /acct is a special case when the directory already exists
-                // TODO: check if file mode is already what we want instead of using EROFS
-                if (errno != EROFS) {
-                    PLOG(ERROR) << "fchmodat() failed for " << path;
-                    return false;
-                }
-            }
-        } else {
-            PLOG(ERROR) << "mkdir() failed for " << path;
-            return false;
-        }
-    }
-
-    if (uid.empty()) {
-        return true;
-    }
-
-    passwd* uid_pwd = getpwnam(uid.c_str());
-    if (!uid_pwd) {
-        PLOG(ERROR) << "Unable to decode UID for '" << uid << "'";
-        return false;
-    }
-
-    uid_t pw_uid = uid_pwd->pw_uid;
-    gid_t gr_gid = -1;
-    if (!gid.empty()) {
-        group* gid_pwd = getgrnam(gid.c_str());
-        if (!gid_pwd) {
-            PLOG(ERROR) << "Unable to decode GID for '" << gid << "'";
-            return false;
-        }
-        gr_gid = gid_pwd->gr_gid;
-    }
-
-    if (lchown(path.c_str(), pw_uid, gr_gid) < 0) {
-        PLOG(ERROR) << "lchown() failed for " << path;
-        return false;
-    }
-
-    /* chown may have cleared S_ISUID and S_ISGID, chmod again */
-    if (mode & (S_ISUID | S_ISGID)) {
-        if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
-            PLOG(ERROR) << "fchmodat() failed for " << path;
-            return false;
-        }
-    }
-
-    return true;
-}
-
-static bool ReadDescriptorsFromFile(const std::string& file_name,
-                                    std::map<std::string, CgroupDescriptor>* descriptors) {
-    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::Reader reader;
-    Json::Value root;
-    if (!reader.parse(json_doc, root)) {
-        LOG(ERROR) << "Failed to parse cgroups description: " << reader.getFormattedErrorMessages();
-        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();
-            auto iter = descriptors->find(name);
-            if (iter == descriptors->end()) {
-                descriptors->emplace(name, CgroupDescriptor(1, name, cgroups[i]["Path"].asString(),
-                                     std::strtoul(cgroups[i]["Mode"].asString().c_str(), 0, 8),
-                                     cgroups[i]["UID"].asString(), cgroups[i]["GID"].asString()));
-            } else {
-                iter->second = CgroupDescriptor(1, name, cgroups[i]["Path"].asString(),
-                                     std::strtoul(cgroups[i]["Mode"].asString().c_str(), 0, 8),
-                                     cgroups[i]["UID"].asString(), cgroups[i]["GID"].asString());
-            }
-        }
-    }
-
-    if (root.isMember("Cgroups2")) {
-        const Json::Value& cgroups2 = root["Cgroups2"];
-        auto iter = descriptors->find(CGROUPV2_CONTROLLER_NAME);
-        if (iter == descriptors->end()) {
-            descriptors->emplace(CGROUPV2_CONTROLLER_NAME, CgroupDescriptor(2, CGROUPV2_CONTROLLER_NAME, cgroups2["Path"].asString(),
-                                 std::strtoul(cgroups2["Mode"].asString().c_str(), 0, 8),
-                                 cgroups2["UID"].asString(), cgroups2["GID"].asString()));
-        } else {
-            iter->second = CgroupDescriptor(2, CGROUPV2_CONTROLLER_NAME, cgroups2["Path"].asString(),
-                                 std::strtoul(cgroups2["Mode"].asString().c_str(), 0, 8),
-                                 cgroups2["UID"].asString(), cgroups2["GID"].asString());
-        }
-    }
-
-    return true;
-}
-
-static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
-    // load system cgroup descriptors
-    if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, 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;
-    }
-
-    return true;
-}
-
-// To avoid issues in sdk_mac build
-#if defined(__ANDROID__)
-
-static bool SetupCgroup(const CgroupDescriptor& descriptor) {
-    const CgroupController* controller = descriptor.controller();
-
-    // mkdir <path> [mode] [owner] [group]
-    if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
-        LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
-        return false;
-    }
-
-    int result;
-    if (controller->version() == 2) {
-        result = mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
-                       nullptr);
-    } else {
-        // Unfortunately historically cpuset controller was mounted using a mount command
-        // different from all other controllers. This results in controller attributes not
-        // to be prepended with controller name. For example this way instead of
-        // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
-        // the system currently expects.
-        if (!strcmp(controller->name(), "cpuset")) {
-            // mount cpuset none /dev/cpuset nodev noexec nosuid
-            result = mount("none", controller->path(), controller->name(),
-                           MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
-        } else {
-            // mount cgroup none <path> nodev noexec nosuid <controller>
-            result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
-                           controller->name());
-        }
-    }
-
-    if (result < 0) {
-        PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
-        return false;
-    }
-
-    return true;
-}
-
-#else
-
-// Stubs for non-Android targets.
-static bool SetupCgroup(const CgroupDescriptor&) {
-    return false;
-}
-
-#endif
-
-// WARNING: This function should be called only from SetupCgroups and only once.
-// It intentionally leaks an FD, so additional invocation will result in additional leak.
-static bool WriteRcFile(const std::map<std::string, CgroupDescriptor>& descriptors) {
-    // WARNING: We are intentionally leaking the FD to keep the file open forever.
-    // Let init keep the FD open to prevent file mappings from becoming invalid in
-    // case the file gets deleted somehow.
-    int fd = TEMP_FAILURE_RETRY(open(CGROUPS_RC_PATH, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
-                                     S_IRUSR | S_IRGRP | S_IROTH));
-    if (fd < 0) {
-        PLOG(ERROR) << "open() failed for " << CGROUPS_RC_PATH;
-        return false;
-    }
-
-    CgroupFile fl;
-    fl.version_ = CgroupFile::FILE_CURR_VERSION;
-    fl.controller_count_ = descriptors.size();
-    int ret = TEMP_FAILURE_RETRY(write(fd, &fl, sizeof(fl)));
-    if (ret < 0) {
-        PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
-        return false;
-    }
-
-    for (const auto& [name, descriptor] : descriptors) {
-        ret = TEMP_FAILURE_RETRY(write(fd, descriptor.controller(), sizeof(CgroupController)));
-        if (ret < 0) {
-            PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
-            return false;
-        }
-    }
-
-    return true;
-}
-
 CgroupController::CgroupController(uint32_t version, const std::string& name,
                                    const std::string& path) {
     version_ = version;
@@ -319,11 +107,6 @@
     return true;
 }
 
-CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
-                                   const std::string& path, mode_t mode, const std::string& uid,
-                                   const std::string& gid)
-    : controller_(version, name, path), mode_(mode), uid_(uid), gid_(gid) {}
-
 CgroupMap::CgroupMap() : cg_file_data_(nullptr), cg_file_size_(0) {
     if (!LoadRcFile()) {
         LOG(ERROR) << "CgroupMap::LoadRcFile called for [" << getpid() << "] failed";
@@ -411,60 +194,6 @@
     }
 }
 
-bool CgroupMap::SetupCgroups() {
-    std::map<std::string, CgroupDescriptor> descriptors;
-
-    if (getpid() != 1) {
-        LOG(ERROR) << "Cgroup setup can be done only by init process";
-        return false;
-    }
-
-    // Make sure we do this only one time. No need for std::call_once because
-    // init is a single-threaded process
-    if (access(CGROUPS_RC_PATH, F_OK) == 0) {
-        LOG(WARNING) << "Attempt to call SetupCgroups more than once";
-        return true;
-    }
-
-    // load cgroups.json file
-    if (!ReadDescriptors(&descriptors)) {
-        LOG(ERROR) << "Failed to load cgroup description file";
-        return false;
-    }
-
-    // setup cgroups
-    for (const auto& [name, descriptor] : descriptors) {
-        if (!SetupCgroup(descriptor)) {
-            // issue a warning and proceed with the next cgroup
-            // TODO: mark the descriptor as invalid and skip it in WriteRcFile()
-            LOG(WARNING) << "Failed to setup " << name << " cgroup";
-        }
-    }
-
-    // mkdir <CGROUPS_RC_DIR> 0711 system system
-    if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) {
-        LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file";
-        return false;
-    }
-
-    // Generate <CGROUPS_RC_FILE> file which can be directly mmapped into
-    // process memory. This optimizes performance, memory usage
-    // and limits infrormation shared with unprivileged processes
-    // to the minimum subset of information from cgroups.json
-    if (!WriteRcFile(descriptors)) {
-        LOG(ERROR) << "Failed to write " << CGROUPS_RC_PATH << " file";
-        return false;
-    }
-
-    // chmod 0644 <CGROUPS_RC_PATH>
-    if (fchmodat(AT_FDCWD, CGROUPS_RC_PATH, 0644, AT_SYMLINK_NOFOLLOW) < 0) {
-        PLOG(ERROR) << "fchmodat() failed";
-        return false;
-    }
-
-    return true;
-}
-
 const CgroupController* CgroupMap::FindController(const std::string& name) const {
     if (!cg_file_data_) {
         LOG(ERROR) << "CgroupMap::FindController called for [" << getpid()
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 304ae15..044510b 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -46,24 +46,6 @@
     char path_[CGROUP_PATH_BUF_SZ];
 };
 
-// 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);
-
-    const CgroupController* controller() const { return &controller_; }
-    mode_t mode() const { return mode_; }
-    std::string uid() const { return uid_; }
-    std::string gid() const { return gid_; }
-
-  private:
-    CgroupController controller_;
-    mode_t mode_;
-    std::string uid_;
-    std::string gid_;
-};
-
 struct CgroupFile {
     static constexpr uint32_t FILE_VERSION_1 = 1;
     static constexpr uint32_t FILE_CURR_VERSION = FILE_VERSION_1;
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 46b8676..86e6035 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -26,7 +26,6 @@
 static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
 static constexpr const char* CGROUPS_RC_PATH = "/dev/cgroup_info/cgroup.rc";
 
-bool CgroupSetupCgroups();
 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
 bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 8884650..3f6b2d7 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -55,10 +55,6 @@
 
 #define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
 
-bool CgroupSetupCgroups() {
-    return CgroupMap::SetupCgroups();
-}
-
 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path) {
     const CgroupController* controller = CgroupMap::GetInstance().FindController(cgroup_name);
 
diff --git a/libprocessgroup/setup/Android.bp b/libprocessgroup/setup/Android.bp
new file mode 100644
index 0000000..a403a23
--- /dev/null
+++ b/libprocessgroup/setup/Android.bp
@@ -0,0 +1,41 @@
+//
+// 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.
+//
+
+cc_library_shared {
+    name: "libprocessgroup_setup",
+    recovery_available: true,
+    srcs: [
+        "cgroup_map_write.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libbase",
+        "libprocessgroup",
+        "libjsoncpp",
+    ],
+    header_libs: [
+        "libprocessgroup_headers",
+    ],
+    export_header_lib_headers: [
+        "libprocessgroup_headers",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/libprocessgroup/setup/cgroup_descriptor.h b/libprocessgroup/setup/cgroup_descriptor.h
new file mode 100644
index 0000000..185c5f9
--- /dev/null
+++ b/libprocessgroup/setup/cgroup_descriptor.h
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+// TODO(b/123664216): Use libcgrouprc_format
+#include "../cgroup_map.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);
+
+    const CgroupController* controller() const { return &controller_; }
+    mode_t mode() const { return mode_; }
+    std::string uid() const { return uid_; }
+    std::string gid() const { return gid_; }
+
+  private:
+    CgroupController controller_;
+    mode_t mode_ = 0;
+    std::string uid_;
+    std::string gid_;
+};
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
new file mode 100644
index 0000000..8ecc0a4
--- /dev/null
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "libprocessgroup"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <regex>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <json/reader.h>
+#include <json/value.h>
+#include <processgroup/processgroup.h>
+#include <processgroup/setup.h>
+
+#include "cgroup_descriptor.h"
+
+using android::base::GetBoolProperty;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
+static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
+static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
+
+static bool Mkdir(const std::string& path, mode_t mode, const std::string& uid,
+                  const std::string& gid) {
+    if (mode == 0) {
+        mode = 0755;
+    }
+
+    if (mkdir(path.c_str(), mode) != 0) {
+        /* chmod in case the directory already exists */
+        if (errno == EEXIST) {
+            if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+                // /acct is a special case when the directory already exists
+                // TODO: check if file mode is already what we want instead of using EROFS
+                if (errno != EROFS) {
+                    PLOG(ERROR) << "fchmodat() failed for " << path;
+                    return false;
+                }
+            }
+        } else {
+            PLOG(ERROR) << "mkdir() failed for " << path;
+            return false;
+        }
+    }
+
+    if (uid.empty()) {
+        return true;
+    }
+
+    passwd* uid_pwd = getpwnam(uid.c_str());
+    if (!uid_pwd) {
+        PLOG(ERROR) << "Unable to decode UID for '" << uid << "'";
+        return false;
+    }
+
+    uid_t pw_uid = uid_pwd->pw_uid;
+    gid_t gr_gid = -1;
+    if (!gid.empty()) {
+        group* gid_pwd = getgrnam(gid.c_str());
+        if (!gid_pwd) {
+            PLOG(ERROR) << "Unable to decode GID for '" << gid << "'";
+            return false;
+        }
+        gr_gid = gid_pwd->gr_gid;
+    }
+
+    if (lchown(path.c_str(), pw_uid, gr_gid) < 0) {
+        PLOG(ERROR) << "lchown() failed for " << path;
+        return false;
+    }
+
+    /* chown may have cleared S_ISUID and S_ISGID, chmod again */
+    if (mode & (S_ISUID | S_ISGID)) {
+        if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+            PLOG(ERROR) << "fchmodat() failed for " << path;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool ReadDescriptorsFromFile(const std::string& file_name,
+                                    std::map<std::string, CgroupDescriptor>* descriptors) {
+    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::Reader reader;
+    Json::Value root;
+    if (!reader.parse(json_doc, root)) {
+        LOG(ERROR) << "Failed to parse cgroups description: " << reader.getFormattedErrorMessages();
+        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();
+            auto iter = descriptors->find(name);
+            if (iter == descriptors->end()) {
+                descriptors->emplace(
+                        name, CgroupDescriptor(
+                                      1, name, cgroups[i]["Path"].asString(),
+                                      std::strtoul(cgroups[i]["Mode"].asString().c_str(), 0, 8),
+                                      cgroups[i]["UID"].asString(), cgroups[i]["GID"].asString()));
+            } else {
+                iter->second = CgroupDescriptor(
+                        1, name, cgroups[i]["Path"].asString(),
+                        std::strtoul(cgroups[i]["Mode"].asString().c_str(), 0, 8),
+                        cgroups[i]["UID"].asString(), cgroups[i]["GID"].asString());
+            }
+        }
+    }
+
+    if (root.isMember("Cgroups2")) {
+        const Json::Value& cgroups2 = root["Cgroups2"];
+        auto iter = descriptors->find(CGROUPV2_CONTROLLER_NAME);
+        if (iter == descriptors->end()) {
+            descriptors->emplace(
+                    CGROUPV2_CONTROLLER_NAME,
+                    CgroupDescriptor(2, CGROUPV2_CONTROLLER_NAME, cgroups2["Path"].asString(),
+                                     std::strtoul(cgroups2["Mode"].asString().c_str(), 0, 8),
+                                     cgroups2["UID"].asString(), cgroups2["GID"].asString()));
+        } else {
+            iter->second =
+                    CgroupDescriptor(2, CGROUPV2_CONTROLLER_NAME, cgroups2["Path"].asString(),
+                                     std::strtoul(cgroups2["Mode"].asString().c_str(), 0, 8),
+                                     cgroups2["UID"].asString(), cgroups2["GID"].asString());
+        }
+    }
+
+    return true;
+}
+
+static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
+    // load system cgroup descriptors
+    if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, 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;
+    }
+
+    return true;
+}
+
+// To avoid issues in sdk_mac build
+#if defined(__ANDROID__)
+
+static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+    const CgroupController* controller = descriptor.controller();
+
+    // mkdir <path> [mode] [owner] [group]
+    if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+        LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+        return false;
+    }
+
+    int result;
+    if (controller->version() == 2) {
+        result = mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+                       nullptr);
+    } else {
+        // Unfortunately historically cpuset controller was mounted using a mount command
+        // different from all other controllers. This results in controller attributes not
+        // to be prepended with controller name. For example this way instead of
+        // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
+        // the system currently expects.
+        if (!strcmp(controller->name(), "cpuset")) {
+            // mount cpuset none /dev/cpuset nodev noexec nosuid
+            result = mount("none", controller->path(), controller->name(),
+                           MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
+        } else {
+            // mount cgroup none <path> nodev noexec nosuid <controller>
+            result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+                           controller->name());
+        }
+    }
+
+    if (result < 0) {
+        PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+        return false;
+    }
+
+    return true;
+}
+
+#else
+
+// Stubs for non-Android targets.
+static bool SetupCgroup(const CgroupDescriptor&) {
+    return false;
+}
+
+#endif
+
+// WARNING: This function should be called only from SetupCgroups and only once.
+// It intentionally leaks an FD, so additional invocation will result in additional leak.
+static bool WriteRcFile(const std::map<std::string, CgroupDescriptor>& descriptors) {
+    // WARNING: We are intentionally leaking the FD to keep the file open forever.
+    // Let init keep the FD open to prevent file mappings from becoming invalid in
+    // case the file gets deleted somehow.
+    int fd = TEMP_FAILURE_RETRY(open(CGROUPS_RC_PATH, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
+                                     S_IRUSR | S_IRGRP | S_IROTH));
+    if (fd < 0) {
+        PLOG(ERROR) << "open() failed for " << CGROUPS_RC_PATH;
+        return false;
+    }
+
+    CgroupFile fl;
+    fl.version_ = CgroupFile::FILE_CURR_VERSION;
+    fl.controller_count_ = descriptors.size();
+    int ret = TEMP_FAILURE_RETRY(write(fd, &fl, sizeof(fl)));
+    if (ret < 0) {
+        PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
+        return false;
+    }
+
+    for (const auto& [name, descriptor] : descriptors) {
+        ret = TEMP_FAILURE_RETRY(write(fd, descriptor.controller(), sizeof(CgroupController)));
+        if (ret < 0) {
+            PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
+                                   const std::string& path, mode_t mode, const std::string& uid,
+                                   const std::string& gid)
+    : controller_(version, name, path), mode_(mode), uid_(uid), gid_(gid) {}
+
+bool CgroupSetupCgroups() {
+    std::map<std::string, CgroupDescriptor> descriptors;
+
+    if (getpid() != 1) {
+        LOG(ERROR) << "Cgroup setup can be done only by init process";
+        return false;
+    }
+
+    // Make sure we do this only one time. No need for std::call_once because
+    // init is a single-threaded process
+    if (access(CGROUPS_RC_PATH, F_OK) == 0) {
+        LOG(WARNING) << "Attempt to call SetupCgroups more than once";
+        return true;
+    }
+
+    // load cgroups.json file
+    if (!ReadDescriptors(&descriptors)) {
+        LOG(ERROR) << "Failed to load cgroup description file";
+        return false;
+    }
+
+    // setup cgroups
+    for (const auto& [name, descriptor] : descriptors) {
+        if (!SetupCgroup(descriptor)) {
+            // issue a warning and proceed with the next cgroup
+            // TODO: mark the descriptor as invalid and skip it in WriteRcFile()
+            LOG(WARNING) << "Failed to setup " << name << " cgroup";
+        }
+    }
+
+    // mkdir <CGROUPS_RC_DIR> 0711 system system
+    if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) {
+        LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file";
+        return false;
+    }
+
+    // Generate <CGROUPS_RC_FILE> file which can be directly mmapped into
+    // process memory. This optimizes performance, memory usage
+    // and limits infrormation shared with unprivileged processes
+    // to the minimum subset of information from cgroups.json
+    if (!WriteRcFile(descriptors)) {
+        LOG(ERROR) << "Failed to write " << CGROUPS_RC_PATH << " file";
+        return false;
+    }
+
+    // chmod 0644 <CGROUPS_RC_PATH>
+    if (fchmodat(AT_FDCWD, CGROUPS_RC_PATH, 0644, AT_SYMLINK_NOFOLLOW) < 0) {
+        PLOG(ERROR) << "fchmodat() failed";
+        return false;
+    }
+
+    return true;
+}
diff --git a/libprocessgroup/setup/include/processgroup/setup.h b/libprocessgroup/setup/include/processgroup/setup.h
new file mode 100644
index 0000000..b488217
--- /dev/null
+++ b/libprocessgroup/setup/include/processgroup/setup.h
@@ -0,0 +1,19 @@
+/*
+ * 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
+
+bool CgroupSetupCgroups();