...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups/fs2

     1  package fs2
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/opencontainers/runc/libcontainer/cgroups"
    10  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    11  	"github.com/opencontainers/runc/libcontainer/configs"
    12  )
    13  
    14  type parseError = fscommon.ParseError
    15  
    16  type manager struct {
    17  	config *configs.Cgroup
    18  	// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
    19  	dirPath string
    20  	// controllers is content of "cgroup.controllers" file.
    21  	// excludes pseudo-controllers ("devices" and "freezer").
    22  	controllers map[string]struct{}
    23  }
    24  
    25  // NewManager creates a manager for cgroup v2 unified hierarchy.
    26  // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
    27  // If dirPath is empty, it is automatically set using config.
    28  func NewManager(config *configs.Cgroup, dirPath string) (cgroups.Manager, error) {
    29  	if dirPath == "" {
    30  		var err error
    31  		dirPath, err = defaultDirPath(config)
    32  		if err != nil {
    33  			return nil, err
    34  		}
    35  	}
    36  
    37  	m := &manager{
    38  		config:  config,
    39  		dirPath: dirPath,
    40  	}
    41  	return m, nil
    42  }
    43  
    44  func (m *manager) getControllers() error {
    45  	if m.controllers != nil {
    46  		return nil
    47  	}
    48  
    49  	data, err := cgroups.ReadFile(m.dirPath, "cgroup.controllers")
    50  	if err != nil {
    51  		if m.config.Rootless && m.config.Path == "" {
    52  			return nil
    53  		}
    54  		return err
    55  	}
    56  	fields := strings.Fields(data)
    57  	m.controllers = make(map[string]struct{}, len(fields))
    58  	for _, c := range fields {
    59  		m.controllers[c] = struct{}{}
    60  	}
    61  
    62  	return nil
    63  }
    64  
    65  func (m *manager) Apply(pid int) error {
    66  	if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
    67  		// Related tests:
    68  		// - "runc create (no limits + no cgrouppath + no permission) succeeds"
    69  		// - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
    70  		// - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
    71  		if m.config.Rootless {
    72  			if m.config.Path == "" {
    73  				if blNeed, nErr := needAnyControllers(m.config.Resources); nErr == nil && !blNeed {
    74  					return nil
    75  				}
    76  				return fmt.Errorf("rootless needs no limits + no cgrouppath when no permission is granted for cgroups: %w", err)
    77  			}
    78  		}
    79  		return err
    80  	}
    81  	if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
    82  		return err
    83  	}
    84  	return nil
    85  }
    86  
    87  func (m *manager) GetPids() ([]int, error) {
    88  	return cgroups.GetPids(m.dirPath)
    89  }
    90  
    91  func (m *manager) GetAllPids() ([]int, error) {
    92  	return cgroups.GetAllPids(m.dirPath)
    93  }
    94  
    95  func (m *manager) GetStats() (*cgroups.Stats, error) {
    96  	var errs []error
    97  
    98  	st := cgroups.NewStats()
    99  
   100  	// pids (since kernel 4.5)
   101  	if err := statPids(m.dirPath, st); err != nil {
   102  		errs = append(errs, err)
   103  	}
   104  	// memory (since kernel 4.5)
   105  	if err := statMemory(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   106  		errs = append(errs, err)
   107  	}
   108  	// io (since kernel 4.5)
   109  	if err := statIo(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   110  		errs = append(errs, err)
   111  	}
   112  	// cpu (since kernel 4.15)
   113  	// Note cpu.stat is available even if the controller is not enabled.
   114  	if err := statCpu(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   115  		errs = append(errs, err)
   116  	}
   117  	// hugetlb (since kernel 5.6)
   118  	if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   119  		errs = append(errs, err)
   120  	}
   121  	// rdma (since kernel 4.11)
   122  	if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) {
   123  		errs = append(errs, err)
   124  	}
   125  	if len(errs) > 0 && !m.config.Rootless {
   126  		return st, fmt.Errorf("error while statting cgroup v2: %+v", errs)
   127  	}
   128  	return st, nil
   129  }
   130  
   131  func (m *manager) Freeze(state configs.FreezerState) error {
   132  	if m.config.Resources == nil {
   133  		return errors.New("cannot toggle freezer: cgroups not configured for container")
   134  	}
   135  	if err := setFreezer(m.dirPath, state); err != nil {
   136  		return err
   137  	}
   138  	m.config.Resources.Freezer = state
   139  	return nil
   140  }
   141  
   142  func (m *manager) Destroy() error {
   143  	return cgroups.RemovePath(m.dirPath)
   144  }
   145  
   146  func (m *manager) Path(_ string) string {
   147  	return m.dirPath
   148  }
   149  
   150  func (m *manager) Set(r *configs.Resources) error {
   151  	if r == nil {
   152  		return nil
   153  	}
   154  	if err := m.getControllers(); err != nil {
   155  		return err
   156  	}
   157  	// pids (since kernel 4.5)
   158  	if err := setPids(m.dirPath, r); err != nil {
   159  		return err
   160  	}
   161  	// memory (since kernel 4.5)
   162  	if err := setMemory(m.dirPath, r); err != nil {
   163  		return err
   164  	}
   165  	// io (since kernel 4.5)
   166  	if err := setIo(m.dirPath, r); err != nil {
   167  		return err
   168  	}
   169  	// cpu (since kernel 4.15)
   170  	if err := setCpu(m.dirPath, r); err != nil {
   171  		return err
   172  	}
   173  	// devices (since kernel 4.15, pseudo-controller)
   174  	//
   175  	// When rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
   176  	// However, errors from other subsystems are not ignored.
   177  	// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
   178  	if err := setDevices(m.dirPath, r); err != nil && !m.config.Rootless {
   179  		return err
   180  	}
   181  	// cpuset (since kernel 5.0)
   182  	if err := setCpuset(m.dirPath, r); err != nil {
   183  		return err
   184  	}
   185  	// hugetlb (since kernel 5.6)
   186  	if err := setHugeTlb(m.dirPath, r); err != nil {
   187  		return err
   188  	}
   189  	// rdma (since kernel 4.11)
   190  	if err := fscommon.RdmaSet(m.dirPath, r); err != nil {
   191  		return err
   192  	}
   193  	// freezer (since kernel 5.2, pseudo-controller)
   194  	if err := setFreezer(m.dirPath, r.Freezer); err != nil {
   195  		return err
   196  	}
   197  	if err := m.setUnified(r.Unified); err != nil {
   198  		return err
   199  	}
   200  	m.config.Resources = r
   201  	return nil
   202  }
   203  
   204  func (m *manager) setUnified(res map[string]string) error {
   205  	for k, v := range res {
   206  		if strings.Contains(k, "/") {
   207  			return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
   208  		}
   209  		if err := cgroups.WriteFile(m.dirPath, k, v); err != nil {
   210  			// Check for both EPERM and ENOENT since O_CREAT is used by WriteFile.
   211  			if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
   212  				// Check if a controller is available,
   213  				// to give more specific error if not.
   214  				sk := strings.SplitN(k, ".", 2)
   215  				if len(sk) != 2 {
   216  					return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k)
   217  				}
   218  				c := sk[0]
   219  				if _, ok := m.controllers[c]; !ok && c != "cgroup" {
   220  					return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, c)
   221  				}
   222  			}
   223  			return fmt.Errorf("unable to set unified resource %q: %w", k, err)
   224  		}
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  func (m *manager) GetPaths() map[string]string {
   231  	paths := make(map[string]string, 1)
   232  	paths[""] = m.dirPath
   233  	return paths
   234  }
   235  
   236  func (m *manager) GetCgroups() (*configs.Cgroup, error) {
   237  	return m.config, nil
   238  }
   239  
   240  func (m *manager) GetFreezerState() (configs.FreezerState, error) {
   241  	return getFreezer(m.dirPath)
   242  }
   243  
   244  func (m *manager) Exists() bool {
   245  	return cgroups.PathExists(m.dirPath)
   246  }
   247  
   248  func OOMKillCount(path string) (uint64, error) {
   249  	return fscommon.GetValueByKey(path, "memory.events", "oom_kill")
   250  }
   251  
   252  func (m *manager) OOMKillCount() (uint64, error) {
   253  	c, err := OOMKillCount(m.dirPath)
   254  	if err != nil && m.config.Rootless && os.IsNotExist(err) {
   255  		err = nil
   256  	}
   257  
   258  	return c, err
   259  }
   260  

View as plain text