github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs2/fs2.go (about)

     1  package fs2
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/opencontainers/runc/libcontainer/cgroups"
    11  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    12  	"github.com/opencontainers/runc/libcontainer/configs"
    13  	"github.com/opencontainers/runc/libcontainer/utils"
    14  )
    15  
    16  type parseError = fscommon.ParseError
    17  
    18  type Manager struct {
    19  	config *configs.Cgroup
    20  	// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
    21  	dirPath string
    22  	// controllers is content of "cgroup.controllers" file.
    23  	// excludes pseudo-controllers ("devices" and "freezer").
    24  	controllers map[string]struct{}
    25  }
    26  
    27  // NewManager creates a manager for cgroup v2 unified hierarchy.
    28  // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
    29  // If dirPath is empty, it is automatically set using config.
    30  func NewManager(config *configs.Cgroup, dirPath string) (*Manager, error) {
    31  	if dirPath == "" {
    32  		var err error
    33  		dirPath, err = defaultDirPath(config)
    34  		if err != nil {
    35  			return nil, err
    36  		}
    37  	} else {
    38  		// Clean path for safety.
    39  		dirPath = utils.CleanPath(dirPath)
    40  	}
    41  
    42  	m := &Manager{
    43  		config:  config,
    44  		dirPath: dirPath,
    45  	}
    46  	return m, nil
    47  }
    48  
    49  func (m *Manager) getControllers() error {
    50  	if m.controllers != nil {
    51  		return nil
    52  	}
    53  
    54  	data, err := cgroups.ReadFile(m.dirPath, "cgroup.controllers")
    55  	if err != nil {
    56  		if m.config.Rootless && m.config.Path == "" {
    57  			return nil
    58  		}
    59  		return err
    60  	}
    61  	fields := strings.Fields(data)
    62  	m.controllers = make(map[string]struct{}, len(fields))
    63  	for _, c := range fields {
    64  		m.controllers[c] = struct{}{}
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  func (m *Manager) Apply(pid int) error {
    71  	if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
    72  		// Related tests:
    73  		// - "runc create (no limits + no cgrouppath + no permission) succeeds"
    74  		// - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
    75  		// - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
    76  		if m.config.Rootless {
    77  			if m.config.Path == "" {
    78  				if blNeed, nErr := needAnyControllers(m.config.Resources); nErr == nil && !blNeed {
    79  					return nil
    80  				}
    81  				return fmt.Errorf("rootless needs no limits + no cgrouppath when no permission is granted for cgroups: %w", err)
    82  			}
    83  		}
    84  		return err
    85  	}
    86  	if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
    87  		return err
    88  	}
    89  	return nil
    90  }
    91  
    92  func (m *Manager) GetPids() ([]int, error) {
    93  	return cgroups.GetPids(m.dirPath)
    94  }
    95  
    96  func (m *Manager) GetAllPids() ([]int, error) {
    97  	return cgroups.GetAllPids(m.dirPath)
    98  }
    99  
   100  func (m *Manager) GetStats() (*cgroups.Stats, error) {
   101  	var errs []error
   102  
   103  	st := cgroups.NewStats()
   104  
   105  	// pids (since kernel 4.5)
   106  	if err := statPids(m.dirPath, st); err != nil {
   107  		errs = append(errs, err)
   108  	}
   109  	// memory (since kernel 4.5)
   110  	if err := statMemory(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   111  		errs = append(errs, err)
   112  	}
   113  	// io (since kernel 4.5)
   114  	if err := statIo(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   115  		errs = append(errs, err)
   116  	}
   117  	// cpu (since kernel 4.15)
   118  	// Note cpu.stat is available even if the controller is not enabled.
   119  	if err := statCpu(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   120  		errs = append(errs, err)
   121  	}
   122  	// PSI (since kernel 4.20).
   123  	var err error
   124  	if st.CpuStats.PSI, err = statPSI(m.dirPath, "cpu.pressure"); err != nil {
   125  		errs = append(errs, err)
   126  	}
   127  	if st.MemoryStats.PSI, err = statPSI(m.dirPath, "memory.pressure"); err != nil {
   128  		errs = append(errs, err)
   129  	}
   130  	if st.BlkioStats.PSI, err = statPSI(m.dirPath, "io.pressure"); err != nil {
   131  		errs = append(errs, err)
   132  	}
   133  	// hugetlb (since kernel 5.6)
   134  	if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   135  		errs = append(errs, err)
   136  	}
   137  	// rdma (since kernel 4.11)
   138  	if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   139  		errs = append(errs, err)
   140  	}
   141  	// misc (since kernel 5.13)
   142  	if err := statMisc(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   143  		errs = append(errs, err)
   144  	}
   145  	if len(errs) > 0 && !m.config.Rootless {
   146  		return st, fmt.Errorf("error while statting cgroup v2: %+v", errs)
   147  	}
   148  	return st, nil
   149  }
   150  
   151  func (m *Manager) Freeze(state configs.FreezerState) error {
   152  	if m.config.Resources == nil {
   153  		return errors.New("cannot toggle freezer: cgroups not configured for container")
   154  	}
   155  	if err := setFreezer(m.dirPath, state); err != nil {
   156  		return err
   157  	}
   158  	m.config.Resources.Freezer = state
   159  	return nil
   160  }
   161  
   162  func (m *Manager) Destroy() error {
   163  	return cgroups.RemovePath(m.dirPath)
   164  }
   165  
   166  func (m *Manager) Path(_ string) string {
   167  	return m.dirPath
   168  }
   169  
   170  func (m *Manager) Set(r *configs.Resources) error {
   171  	if r == nil {
   172  		return nil
   173  	}
   174  	if err := m.getControllers(); err != nil {
   175  		return err
   176  	}
   177  	// pids (since kernel 4.5)
   178  	if err := setPids(m.dirPath, r); err != nil {
   179  		return err
   180  	}
   181  	// memory (since kernel 4.5)
   182  	if err := setMemory(m.dirPath, r); err != nil {
   183  		return err
   184  	}
   185  	// io (since kernel 4.5)
   186  	if err := setIo(m.dirPath, r); err != nil {
   187  		return err
   188  	}
   189  	// cpu (since kernel 4.15)
   190  	if err := setCpu(m.dirPath, r); err != nil {
   191  		return err
   192  	}
   193  	// devices (since kernel 4.15, pseudo-controller)
   194  	//
   195  	// When rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
   196  	// However, errors from other subsystems are not ignored.
   197  	// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
   198  	if err := setDevices(m.dirPath, r); err != nil {
   199  		if !m.config.Rootless || errors.Is(err, cgroups.ErrDevicesUnsupported) {
   200  			return err
   201  		}
   202  	}
   203  	// cpuset (since kernel 5.0)
   204  	if err := setCpuset(m.dirPath, r); err != nil {
   205  		return err
   206  	}
   207  	// hugetlb (since kernel 5.6)
   208  	if err := setHugeTlb(m.dirPath, r); err != nil {
   209  		return err
   210  	}
   211  	// rdma (since kernel 4.11)
   212  	if err := fscommon.RdmaSet(m.dirPath, r); err != nil {
   213  		return err
   214  	}
   215  	// freezer (since kernel 5.2, pseudo-controller)
   216  	if err := setFreezer(m.dirPath, r.Freezer); err != nil {
   217  		return err
   218  	}
   219  	if err := m.setUnified(r.Unified); err != nil {
   220  		return err
   221  	}
   222  	m.config.Resources = r
   223  	return nil
   224  }
   225  
   226  func setDevices(dirPath string, r *configs.Resources) error {
   227  	if cgroups.DevicesSetV2 == nil {
   228  		if len(r.Devices) > 0 {
   229  			return cgroups.ErrDevicesUnsupported
   230  		}
   231  		return nil
   232  	}
   233  	return cgroups.DevicesSetV2(dirPath, r)
   234  }
   235  
   236  func (m *Manager) setUnified(res map[string]string) error {
   237  	for k, v := range res {
   238  		if strings.Contains(k, "/") {
   239  			return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
   240  		}
   241  		if err := cgroups.WriteFile(m.dirPath, k, v); err != nil {
   242  			// Check for both EPERM and ENOENT since O_CREAT is used by WriteFile.
   243  			if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
   244  				// Check if a controller is available,
   245  				// to give more specific error if not.
   246  				sk := strings.SplitN(k, ".", 2)
   247  				if len(sk) != 2 {
   248  					return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k)
   249  				}
   250  				c := sk[0]
   251  				if _, ok := m.controllers[c]; !ok && c != "cgroup" {
   252  					return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, c)
   253  				}
   254  			}
   255  			return fmt.Errorf("unable to set unified resource %q: %w", k, err)
   256  		}
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  func (m *Manager) GetPaths() map[string]string {
   263  	paths := make(map[string]string, 1)
   264  	paths[""] = m.dirPath
   265  	return paths
   266  }
   267  
   268  func (m *Manager) GetCgroups() (*configs.Cgroup, error) {
   269  	return m.config, nil
   270  }
   271  
   272  func (m *Manager) GetFreezerState() (configs.FreezerState, error) {
   273  	return getFreezer(m.dirPath)
   274  }
   275  
   276  func (m *Manager) Exists() bool {
   277  	return cgroups.PathExists(m.dirPath)
   278  }
   279  
   280  func OOMKillCount(path string) (uint64, error) {
   281  	return fscommon.GetValueByKey(path, "memory.events", "oom_kill")
   282  }
   283  
   284  func (m *Manager) OOMKillCount() (uint64, error) {
   285  	c, err := OOMKillCount(m.dirPath)
   286  	if err != nil && m.config.Rootless && os.IsNotExist(err) {
   287  		err = nil
   288  	}
   289  
   290  	return c, err
   291  }
   292  
   293  func CheckMemoryUsage(dirPath string, r *configs.Resources) error {
   294  	if !r.MemoryCheckBeforeUpdate {
   295  		return nil
   296  	}
   297  
   298  	if r.Memory <= 0 && r.MemorySwap <= 0 {
   299  		return nil
   300  	}
   301  
   302  	usage, err := fscommon.GetCgroupParamUint(dirPath, "memory.current")
   303  	if err != nil {
   304  		// This check is on best-effort basis, so if we can't read the
   305  		// current usage (cgroup not yet created, or any other error),
   306  		// we should not fail.
   307  		return nil
   308  	}
   309  
   310  	if r.MemorySwap > 0 {
   311  		if uint64(r.MemorySwap) <= usage {
   312  			return fmt.Errorf("rejecting memory+swap limit %d <= usage %d", r.MemorySwap, usage)
   313  		}
   314  	}
   315  
   316  	if r.Memory > 0 {
   317  		if uint64(r.Memory) <= usage {
   318  			return fmt.Errorf("rejecting memory limit %d <= usage %d", r.Memory, usage)
   319  		}
   320  	}
   321  
   322  	return nil
   323  }
   324  
   325  func (m *Manager) GetEffectiveCPUs() string {
   326  	// Fast path.
   327  	if m.config.CpusetCpus != "" {
   328  		return m.config.CpusetCpus
   329  	} else if !strings.HasPrefix(m.dirPath, UnifiedMountpoint) {
   330  		return ""
   331  	}
   332  
   333  	// Iterates until it goes outside of the cgroup root path.
   334  	// It's required for containers in which cpuset controller
   335  	// is not enabled, in this case a parent cgroup is used.
   336  	outsidePath := filepath.Dir(UnifiedMountpoint)
   337  
   338  	for path := m.dirPath; path != outsidePath; path = filepath.Dir(path) {
   339  		cpus, err := fscommon.GetCgroupParamString(path, "cpuset.cpus.effective")
   340  		if err == nil {
   341  			return cpus
   342  		}
   343  	}
   344  
   345  	return ""
   346  }