...

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

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

     1  package fs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"sync"
     8  
     9  	"golang.org/x/sys/unix"
    10  
    11  	"github.com/opencontainers/runc/libcontainer/cgroups"
    12  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    13  	"github.com/opencontainers/runc/libcontainer/configs"
    14  )
    15  
    16  var subsystems = []subsystem{
    17  	&CpusetGroup{},
    18  	&DevicesGroup{},
    19  	&MemoryGroup{},
    20  	&CpuGroup{},
    21  	&CpuacctGroup{},
    22  	&PidsGroup{},
    23  	&BlkioGroup{},
    24  	&HugetlbGroup{},
    25  	&NetClsGroup{},
    26  	&NetPrioGroup{},
    27  	&PerfEventGroup{},
    28  	&FreezerGroup{},
    29  	&RdmaGroup{},
    30  	&NameGroup{GroupName: "name=systemd", Join: true},
    31  	&NameGroup{GroupName: "misc", Join: true},
    32  }
    33  
    34  var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
    35  
    36  func init() {
    37  	// If using cgroups-hybrid mode then add a "" controller indicating
    38  	// it should join the cgroups v2.
    39  	if cgroups.IsCgroup2HybridMode() {
    40  		subsystems = append(subsystems, &NameGroup{GroupName: "", Join: true})
    41  	}
    42  }
    43  
    44  type subsystem interface {
    45  	// Name returns the name of the subsystem.
    46  	Name() string
    47  	// GetStats fills in the stats for the subsystem.
    48  	GetStats(path string, stats *cgroups.Stats) error
    49  	// Apply creates and joins a cgroup, adding pid into it. Some
    50  	// subsystems use resources to pre-configure the cgroup parents
    51  	// before creating or joining it.
    52  	Apply(path string, r *configs.Resources, pid int) error
    53  	// Set sets the cgroup resources.
    54  	Set(path string, r *configs.Resources) error
    55  }
    56  
    57  type manager struct {
    58  	mu      sync.Mutex
    59  	cgroups *configs.Cgroup
    60  	paths   map[string]string
    61  }
    62  
    63  func NewManager(cg *configs.Cgroup, paths map[string]string) (cgroups.Manager, error) {
    64  	// Some v1 controllers (cpu, cpuset, and devices) expect
    65  	// cgroups.Resources to not be nil in Apply.
    66  	if cg.Resources == nil {
    67  		return nil, errors.New("cgroup v1 manager needs configs.Resources to be set during manager creation")
    68  	}
    69  	if cg.Resources.Unified != nil {
    70  		return nil, cgroups.ErrV1NoUnified
    71  	}
    72  
    73  	if paths == nil {
    74  		var err error
    75  		paths, err = initPaths(cg)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  	}
    80  
    81  	return &manager{
    82  		cgroups: cg,
    83  		paths:   paths,
    84  	}, nil
    85  }
    86  
    87  // isIgnorableError returns whether err is a permission error (in the loose
    88  // sense of the word). This includes EROFS (which for an unprivileged user is
    89  // basically a permission error) and EACCES (for similar reasons) as well as
    90  // the normal EPERM.
    91  func isIgnorableError(rootless bool, err error) bool {
    92  	// We do not ignore errors if we are root.
    93  	if !rootless {
    94  		return false
    95  	}
    96  	// Is it an ordinary EPERM?
    97  	if errors.Is(err, os.ErrPermission) {
    98  		return true
    99  	}
   100  	// Handle some specific syscall errors.
   101  	var errno unix.Errno
   102  	if errors.As(err, &errno) {
   103  		return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
   104  	}
   105  	return false
   106  }
   107  
   108  func (m *manager) Apply(pid int) (err error) {
   109  	m.mu.Lock()
   110  	defer m.mu.Unlock()
   111  
   112  	c := m.cgroups
   113  
   114  	for _, sys := range subsystems {
   115  		name := sys.Name()
   116  		p, ok := m.paths[name]
   117  		if !ok {
   118  			continue
   119  		}
   120  
   121  		if err := sys.Apply(p, c.Resources, pid); err != nil {
   122  			// In the case of rootless (including euid=0 in userns), where an
   123  			// explicit cgroup path hasn't been set, we don't bail on error in
   124  			// case of permission problems here, but do delete the path from
   125  			// the m.paths map, since it is either non-existent and could not
   126  			// be created, or the pid could not be added to it.
   127  			//
   128  			// Cases where limits for the subsystem have been set are handled
   129  			// later by Set, which fails with a friendly error (see
   130  			// if path == "" in Set).
   131  			if isIgnorableError(c.Rootless, err) && c.Path == "" {
   132  				delete(m.paths, name)
   133  				continue
   134  			}
   135  			return err
   136  		}
   137  
   138  	}
   139  	return nil
   140  }
   141  
   142  func (m *manager) Destroy() error {
   143  	m.mu.Lock()
   144  	defer m.mu.Unlock()
   145  	return cgroups.RemovePaths(m.paths)
   146  }
   147  
   148  func (m *manager) Path(subsys string) string {
   149  	m.mu.Lock()
   150  	defer m.mu.Unlock()
   151  	return m.paths[subsys]
   152  }
   153  
   154  func (m *manager) GetStats() (*cgroups.Stats, error) {
   155  	m.mu.Lock()
   156  	defer m.mu.Unlock()
   157  	stats := cgroups.NewStats()
   158  	for _, sys := range subsystems {
   159  		path := m.paths[sys.Name()]
   160  		if path == "" {
   161  			continue
   162  		}
   163  		if err := sys.GetStats(path, stats); err != nil {
   164  			return nil, err
   165  		}
   166  	}
   167  	return stats, nil
   168  }
   169  
   170  func (m *manager) Set(r *configs.Resources) error {
   171  	if r == nil {
   172  		return nil
   173  	}
   174  
   175  	if r.Unified != nil {
   176  		return cgroups.ErrV1NoUnified
   177  	}
   178  
   179  	m.mu.Lock()
   180  	defer m.mu.Unlock()
   181  	for _, sys := range subsystems {
   182  		path := m.paths[sys.Name()]
   183  		if err := sys.Set(path, r); err != nil {
   184  			// When rootless is true, errors from the device subsystem
   185  			// are ignored, as it is really not expected to work.
   186  			if m.cgroups.Rootless && sys.Name() == "devices" {
   187  				continue
   188  			}
   189  			// However, errors from other subsystems are not ignored.
   190  			// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
   191  			if path == "" {
   192  				// We never created a path for this cgroup, so we cannot set
   193  				// limits for it (though we have already tried at this point).
   194  				return fmt.Errorf("cannot set %s limit: container could not join or create cgroup", sys.Name())
   195  			}
   196  			return err
   197  		}
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  // Freeze toggles the container's freezer cgroup depending on the state
   204  // provided
   205  func (m *manager) Freeze(state configs.FreezerState) error {
   206  	path := m.Path("freezer")
   207  	if path == "" {
   208  		return errors.New("cannot toggle freezer: cgroups not configured for container")
   209  	}
   210  
   211  	prevState := m.cgroups.Resources.Freezer
   212  	m.cgroups.Resources.Freezer = state
   213  	freezer := &FreezerGroup{}
   214  	if err := freezer.Set(path, m.cgroups.Resources); err != nil {
   215  		m.cgroups.Resources.Freezer = prevState
   216  		return err
   217  	}
   218  	return nil
   219  }
   220  
   221  func (m *manager) GetPids() ([]int, error) {
   222  	return cgroups.GetPids(m.Path("devices"))
   223  }
   224  
   225  func (m *manager) GetAllPids() ([]int, error) {
   226  	return cgroups.GetAllPids(m.Path("devices"))
   227  }
   228  
   229  func (m *manager) GetPaths() map[string]string {
   230  	m.mu.Lock()
   231  	defer m.mu.Unlock()
   232  	return m.paths
   233  }
   234  
   235  func (m *manager) GetCgroups() (*configs.Cgroup, error) {
   236  	return m.cgroups, nil
   237  }
   238  
   239  func (m *manager) GetFreezerState() (configs.FreezerState, error) {
   240  	dir := m.Path("freezer")
   241  	// If the container doesn't have the freezer cgroup, say it's undefined.
   242  	if dir == "" {
   243  		return configs.Undefined, nil
   244  	}
   245  	freezer := &FreezerGroup{}
   246  	return freezer.GetState(dir)
   247  }
   248  
   249  func (m *manager) Exists() bool {
   250  	return cgroups.PathExists(m.Path("devices"))
   251  }
   252  
   253  func OOMKillCount(path string) (uint64, error) {
   254  	return fscommon.GetValueByKey(path, "memory.oom_control", "oom_kill")
   255  }
   256  
   257  func (m *manager) OOMKillCount() (uint64, error) {
   258  	c, err := OOMKillCount(m.Path("memory"))
   259  	// Ignore ENOENT when rootless as it couldn't create cgroup.
   260  	if err != nil && m.cgroups.Rootless && os.IsNotExist(err) {
   261  		err = nil
   262  	}
   263  
   264  	return c, err
   265  }
   266  

View as plain text