From c53638e9f61e61b1344cbf090c9766a1891c8a44 Mon Sep 17 00:00:00 2001
From: flouthoc <flouthoc.git@gmail.com>
Date: Wed, 19 May 2021 01:19:20 +0530
Subject: Podman info add support for status of cgroup controllers

Signed-off-by: flouthoc <flouthoc.git@gmail.com>
---
 docs/source/markdown/podman-info.1.md |  13 +++++
 libpod/define/info.go                 |  31 +++++-----
 libpod/info.go                        |  44 ++++++++------
 pkg/cgroups/cgroups.go                | 106 +++++++++++++++++++++++++++++++---
 test/e2e/info_test.go                 |  10 ++++
 5 files changed, 163 insertions(+), 41 deletions(-)

diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md
index 4af51d3eb..227fbd92d 100644
--- a/docs/source/markdown/podman-info.1.md
+++ b/docs/source/markdown/podman-info.1.md
@@ -32,6 +32,12 @@ $ podman info
 host:
   arch: amd64
   buildahVersion: 1.19.0-dev
+  cgroupControllers:
+  - cpuset
+  - cpu
+  - io
+  - memory
+  - pids
   cgroupManager: systemd
   cgroupVersion: v2
   conmon:
@@ -145,6 +151,13 @@ Run podman info with JSON formatted response:
     "buildahVersion": "1.19.0-dev",
     "cgroupManager": "systemd",
     "cgroupVersion": "v2",
+    "cgroupControllers": [
+      "cpuset",
+      "cpu",
+      "io",
+      "memory",
+      "pids"
+    ],
     "conmon": {
       "package": "conmon-2.0.22-2.fc33.x86_64",
       "path": "/usr/bin/conmon",
diff --git a/libpod/define/info.go b/libpod/define/info.go
index c9d6877c0..de709be74 100644
--- a/libpod/define/info.go
+++ b/libpod/define/info.go
@@ -23,21 +23,22 @@ type SecurityInfo struct {
 
 // HostInfo describes the libpod host
 type HostInfo struct {
-	Arch           string           `json:"arch"`
-	BuildahVersion string           `json:"buildahVersion"`
-	CgroupManager  string           `json:"cgroupManager"`
-	CGroupsVersion string           `json:"cgroupVersion"`
-	Conmon         *ConmonInfo      `json:"conmon"`
-	CPUs           int              `json:"cpus"`
-	Distribution   DistributionInfo `json:"distribution"`
-	EventLogger    string           `json:"eventLogger"`
-	Hostname       string           `json:"hostname"`
-	IDMappings     IDMappings       `json:"idMappings,omitempty"`
-	Kernel         string           `json:"kernel"`
-	MemFree        int64            `json:"memFree"`
-	MemTotal       int64            `json:"memTotal"`
-	OCIRuntime     *OCIRuntimeInfo  `json:"ociRuntime"`
-	OS             string           `json:"os"`
+	Arch              string           `json:"arch"`
+	BuildahVersion    string           `json:"buildahVersion"`
+	CgroupManager     string           `json:"cgroupManager"`
+	CGroupsVersion    string           `json:"cgroupVersion"`
+	CgroupControllers []string         `json:"cgroupControllers"`
+	Conmon            *ConmonInfo      `json:"conmon"`
+	CPUs              int              `json:"cpus"`
+	Distribution      DistributionInfo `json:"distribution"`
+	EventLogger       string           `json:"eventLogger"`
+	Hostname          string           `json:"hostname"`
+	IDMappings        IDMappings       `json:"idMappings,omitempty"`
+	Kernel            string           `json:"kernel"`
+	MemFree           int64            `json:"memFree"`
+	MemTotal          int64            `json:"memTotal"`
+	OCIRuntime        *OCIRuntimeInfo  `json:"ociRuntime"`
+	OS                string           `json:"os"`
 	// RemoteSocket returns the UNIX domain socket the Podman service is listening on
 	RemoteSocket *RemoteSocket          `json:"remoteSocket,omitempty"`
 	RuntimeInfo  map[string]interface{} `json:"runtimeInfo,omitempty"`
diff --git a/libpod/info.go b/libpod/info.go
index 7a28a4cf7..461e39a48 100644
--- a/libpod/info.go
+++ b/libpod/info.go
@@ -93,20 +93,33 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
 		return nil, errors.Wrapf(err, "error getting Seccomp profile path")
 	}
 
+	// CGroups version
+	unified, err := cgroups.IsCgroup2UnifiedMode()
+	if err != nil {
+		return nil, errors.Wrapf(err, "error reading cgroups mode")
+	}
+
+	// Get Map of all available controllers
+	availableControllers, err := cgroups.GetAvailableControllers(nil, unified)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error getting available cgroup controllers")
+	}
+
 	info := define.HostInfo{
-		Arch:           runtime.GOARCH,
-		BuildahVersion: buildah.Version,
-		CgroupManager:  r.config.Engine.CgroupManager,
-		Linkmode:       linkmode.Linkmode(),
-		CPUs:           runtime.NumCPU(),
-		Distribution:   hostDistributionInfo,
-		EventLogger:    r.eventer.String(),
-		Hostname:       host,
-		IDMappings:     define.IDMappings{},
-		Kernel:         kv,
-		MemFree:        mi.MemFree,
-		MemTotal:       mi.MemTotal,
-		OS:             runtime.GOOS,
+		Arch:              runtime.GOARCH,
+		BuildahVersion:    buildah.Version,
+		CgroupManager:     r.config.Engine.CgroupManager,
+		CgroupControllers: availableControllers,
+		Linkmode:          linkmode.Linkmode(),
+		CPUs:              runtime.NumCPU(),
+		Distribution:      hostDistributionInfo,
+		EventLogger:       r.eventer.String(),
+		Hostname:          host,
+		IDMappings:        define.IDMappings{},
+		Kernel:            kv,
+		MemFree:           mi.MemFree,
+		MemTotal:          mi.MemTotal,
+		OS:                runtime.GOOS,
 		Security: define.SecurityInfo{
 			AppArmorEnabled:     apparmor.IsEnabled(),
 			DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities, ","),
@@ -120,11 +133,6 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
 		SwapTotal:   mi.SwapTotal,
 	}
 
-	// CGroups version
-	unified, err := cgroups.IsCgroup2UnifiedMode()
-	if err != nil {
-		return nil, errors.Wrapf(err, "error reading cgroups mode")
-	}
 	cgroupVersion := "v1"
 	if unified {
 		cgroupVersion = "v2"
diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go
index aefb5183b..911edeb5b 100644
--- a/pkg/cgroups/cgroups.go
+++ b/pkg/cgroups/cgroups.go
@@ -128,28 +128,118 @@ func init() {
 // getAvailableControllers get the available controllers
 func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
 	if cgroup2 {
-		return nil, fmt.Errorf("getAvailableControllers not implemented yet for cgroup v2")
+		controllers := []controller{}
+		subtreeControl := cgroupRoot + "/cgroup.subtree_control"
+		// rootless cgroupv2: check available controllers for current user ,systemd or servicescope will inherit
+		if rootless.IsRootless() {
+			userSlice, err := getCgroupPathForCurrentProcess()
+			if err != nil {
+				return controllers, err
+			}
+			//userSlice already contains '/' so not adding here
+			basePath := cgroupRoot + userSlice
+			subtreeControl = fmt.Sprintf("%s/cgroup.subtree_control", basePath)
+		}
+		subtreeControlBytes, err := ioutil.ReadFile(subtreeControl)
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", subtreeControl)
+		}
+		for _, controllerName := range strings.Fields(string(subtreeControlBytes)) {
+			c := controller{
+				name:    controllerName,
+				symlink: false,
+			}
+			controllers = append(controllers, c)
+		}
+		return controllers, nil
 	}
 
-	infos, err := ioutil.ReadDir(cgroupRoot)
-	if err != nil {
-		return nil, err
-	}
+	subsystems, _ := cgroupV1GetAllSubsystems()
 	controllers := []controller{}
-	for _, i := range infos {
-		name := i.Name()
+	// cgroupv1 and rootless: No subsystem is available: delegation is unsafe.
+	if rootless.IsRootless() {
+		return controllers, nil
+	}
+
+	for _, name := range subsystems {
 		if _, found := exclude[name]; found {
 			continue
 		}
+		isSymLink := false
+		fileInfo, err := os.Stat(cgroupRoot + "/" + name)
+		if err != nil {
+			isSymLink = !fileInfo.IsDir()
+		}
 		c := controller{
 			name:    name,
-			symlink: !i.IsDir(),
+			symlink: isSymLink,
 		}
 		controllers = append(controllers, c)
 	}
+
 	return controllers, nil
 }
 
+// GetAvailableControllers get string:bool map of all the available controllers
+func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
+	availableControllers, err := getAvailableControllers(exclude, cgroup2)
+	if err != nil {
+		return nil, err
+	}
+	controllerList := []string{}
+	for _, controller := range availableControllers {
+		controllerList = append(controllerList, controller.name)
+	}
+
+	return controllerList, nil
+}
+
+func cgroupV1GetAllSubsystems() ([]string, error) {
+	f, err := os.Open("/proc/cgroups")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	subsystems := []string{}
+
+	s := bufio.NewScanner(f)
+	for s.Scan() {
+		text := s.Text()
+		if text[0] != '#' {
+			parts := strings.Fields(text)
+			if len(parts) >= 4 && parts[3] != "0" {
+				subsystems = append(subsystems, parts[0])
+			}
+		}
+	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
+	return subsystems, nil
+}
+
+func getCgroupPathForCurrentProcess() (string, error) {
+	path := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
+	f, err := os.Open(path)
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+
+	cgroupPath := ""
+	s := bufio.NewScanner(f)
+	for s.Scan() {
+		text := s.Text()
+		procEntries := strings.SplitN(text, "::", 2)
+		cgroupPath = procEntries[1]
+	}
+	if err := s.Err(); err != nil {
+		return cgroupPath, err
+	}
+	return cgroupPath, nil
+}
+
 // getCgroupv1Path is a helper function to get the cgroup v1 path
 func (c *CgroupControl) getCgroupv1Path(name string) string {
 	return filepath.Join(cgroupRoot, name, c.path)
diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go
index 60136bcc2..f5b70d6bf 100644
--- a/test/e2e/info_test.go
+++ b/test/e2e/info_test.go
@@ -135,4 +135,14 @@ var _ = Describe("Podman Info", func() {
 			Expect(session.OutputToString()).To(ContainSubstring("false"))
 		}
 	})
+
+	It("Podman info must contain cgroupControllers with ReleventControllers", func() {
+		SkipIfRootless("Hard to tell which controllers are going to be enabled for rootless")
+		SkipIfRootlessCgroupsV1("Disable cgroups not supported on cgroupv1 for rootless users")
+		session := podmanTest.Podman([]string{"info", "--format", "{{.Host.CgroupControllers}}"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).To(Exit(0))
+		Expect(session.OutputToString()).To(ContainSubstring("memory"))
+		Expect(session.OutputToString()).To(ContainSubstring("pids"))
+	})
 })
-- 
cgit v1.2.3-54-g00ecf