github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/parent/cgrouputil/cgrouputil.go (about)

     1  package cgrouputil
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/moby/sys/mountinfo"
    11  
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  // EvacuateCgroup2 evacuates cgroup2. Must be called in the parent PID namespace.
    16  //
    17  // When the current process belongs to "/foo" group (visible under "/sys/fs/cgroup/foo") and evac is like "bar",
    18  // - All processes in the "/foo" group are moved to "/foo/bar" group, by writing PIDs into "/sys/fs/cgroup/foo/bar/cgroup.procs"
    19  // - As many controllers as possible are enabled for "/foo/*" groups, by writing "/sys/fs/cgroup/foo/cgroup.subtree_control"
    20  //
    21  // Returns nil when cgroup2 is not enabled.
    22  // Ported from https://github.com/rootless-containers/usernetes/commit/46ad812db7489914897ff8b1774f2fab0efda62b
    23  func EvacuateCgroup2(evac string) error {
    24  	if evac == "" {
    25  		return errors.New("got empty evacuation group name")
    26  	}
    27  	if strings.Contains(evac, "/") {
    28  		return fmt.Errorf("unexpected evacuation group name %q: must not contain \"/\"", evac)
    29  	}
    30  
    31  	mountpoint := findCgroup2Mountpoint()
    32  	if mountpoint == "" {
    33  		logrus.Warn("cgroup2 is not mounted. cgroup2 evacuation is discarded.")
    34  		return nil
    35  	}
    36  
    37  	oldGroup := getCgroup2(os.Getpid())
    38  	if mountpoint == "" {
    39  		logrus.Warn("process is not running with cgroup2. cgroup2 evacuation is discarded.")
    40  		return nil
    41  	}
    42  
    43  	newGroup := filepath.Join(oldGroup, evac)
    44  
    45  	oldPath := filepath.Join(mountpoint, oldGroup)
    46  	newPath := filepath.Join(mountpoint, newGroup)
    47  
    48  	if err := os.MkdirAll(newPath, 0755); err != nil {
    49  		return err
    50  	}
    51  
    52  	// evacuate existing procs from oldGroup to newGroup, so that we can enable all controllers including threaded ones
    53  	cgroupProcsBytes, err := os.ReadFile(filepath.Join(oldPath, "cgroup.procs"))
    54  	if err != nil {
    55  		return err
    56  	}
    57  	for _, pidStr := range strings.Split(string(cgroupProcsBytes), "\n") {
    58  		if pidStr == "" || pidStr == "0" {
    59  			continue
    60  		}
    61  		if err := os.WriteFile(filepath.Join(newPath, "cgroup.procs"), []byte(pidStr), 0644); err != nil {
    62  			logrus.WithError(err).Warnf("failed to move process %s to cgroup %q", pidStr, newGroup)
    63  		}
    64  	}
    65  
    66  	// enable controllers for all subgroups under the oldGroup
    67  	controllerBytes, err := os.ReadFile(filepath.Join(oldPath, "cgroup.controllers"))
    68  	if err != nil {
    69  		return err
    70  	}
    71  	for _, controller := range strings.Fields(string(controllerBytes)) {
    72  		logrus.Debugf("enabling controller %q", controller)
    73  		if err := os.WriteFile(filepath.Join(oldPath, "cgroup.subtree_control"), []byte("+"+controller), 0644); err != nil {
    74  			logrus.WithError(err).Warnf("failed to enable controller %q", controller)
    75  		}
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  func findCgroup2Mountpoint() string {
    82  	f := mountinfoFSTypeFilter("cgroup2")
    83  	mounts, err := mountinfo.GetMounts(f)
    84  	if err != nil {
    85  		logrus.WithError(err).Warn("failed to find mountpoint for cgroup2")
    86  		return ""
    87  	}
    88  	if len(mounts) == 0 {
    89  		return ""
    90  	}
    91  	if len(mounts) != 1 {
    92  		logrus.Warnf("expected single mountpoint for cgroup2, got %d", len(mounts))
    93  	}
    94  	return mounts[0].Mountpoint
    95  }
    96  
    97  func getCgroup2(pid int) string {
    98  	p := fmt.Sprintf("/proc/%d/cgroup", pid)
    99  	b, err := os.ReadFile(p)
   100  	if err != nil {
   101  		logrus.WithError(err).Warnf("failed to read %q", p)
   102  		return ""
   103  	}
   104  	return getCgroup2FromProcPidCgroup(b)
   105  }
   106  
   107  func getCgroup2FromProcPidCgroup(b []byte) string {
   108  	for _, l := range strings.Split(string(b), "\n") {
   109  		if strings.HasPrefix(l, "0::") {
   110  			return strings.TrimPrefix(l, "0::")
   111  		}
   112  	}
   113  	return ""
   114  }