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
38
39 if cgroups.IsCgroup2HybridMode() {
40 subsystems = append(subsystems, &NameGroup{GroupName: "", Join: true})
41 }
42 }
43
44 type subsystem interface {
45
46 Name() string
47
48 GetStats(path string, stats *cgroups.Stats) error
49
50
51
52 Apply(path string, r *configs.Resources, pid int) error
53
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
65
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
88
89
90
91 func isIgnorableError(rootless bool, err error) bool {
92
93 if !rootless {
94 return false
95 }
96
97 if errors.Is(err, os.ErrPermission) {
98 return true
99 }
100
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
123
124
125
126
127
128
129
130
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
185
186 if m.cgroups.Rootless && sys.Name() == "devices" {
187 continue
188 }
189
190
191 if path == "" {
192
193
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
204
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
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
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