1 package fs
2
3 import (
4 "errors"
5 "os"
6 "path/filepath"
7 "strconv"
8 "strings"
9
10 "golang.org/x/sys/unix"
11
12 "github.com/opencontainers/runc/libcontainer/cgroups"
13 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
14 "github.com/opencontainers/runc/libcontainer/configs"
15 )
16
17 type CpusetGroup struct{}
18
19 func (s *CpusetGroup) Name() string {
20 return "cpuset"
21 }
22
23 func (s *CpusetGroup) Apply(path string, r *configs.Resources, pid int) error {
24 return s.ApplyDir(path, r, pid)
25 }
26
27 func (s *CpusetGroup) Set(path string, r *configs.Resources) error {
28 if r.CpusetCpus != "" {
29 if err := cgroups.WriteFile(path, "cpuset.cpus", r.CpusetCpus); err != nil {
30 return err
31 }
32 }
33 if r.CpusetMems != "" {
34 if err := cgroups.WriteFile(path, "cpuset.mems", r.CpusetMems); err != nil {
35 return err
36 }
37 }
38 return nil
39 }
40
41 func getCpusetStat(path string, file string) ([]uint16, error) {
42 var extracted []uint16
43 fileContent, err := fscommon.GetCgroupParamString(path, file)
44 if err != nil {
45 return extracted, err
46 }
47 if len(fileContent) == 0 {
48 return extracted, &parseError{Path: path, File: file, Err: errors.New("empty file")}
49 }
50
51 for _, s := range strings.Split(fileContent, ",") {
52 sp := strings.SplitN(s, "-", 3)
53 switch len(sp) {
54 case 3:
55 return extracted, &parseError{Path: path, File: file, Err: errors.New("extra dash")}
56 case 2:
57 min, err := strconv.ParseUint(sp[0], 10, 16)
58 if err != nil {
59 return extracted, &parseError{Path: path, File: file, Err: err}
60 }
61 max, err := strconv.ParseUint(sp[1], 10, 16)
62 if err != nil {
63 return extracted, &parseError{Path: path, File: file, Err: err}
64 }
65 if min > max {
66 return extracted, &parseError{Path: path, File: file, Err: errors.New("invalid values, min > max")}
67 }
68 for i := min; i <= max; i++ {
69 extracted = append(extracted, uint16(i))
70 }
71 case 1:
72 value, err := strconv.ParseUint(s, 10, 16)
73 if err != nil {
74 return extracted, &parseError{Path: path, File: file, Err: err}
75 }
76 extracted = append(extracted, uint16(value))
77 }
78 }
79
80 return extracted, nil
81 }
82
83 func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
84 var err error
85
86 stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
87 if err != nil && !errors.Is(err, os.ErrNotExist) {
88 return err
89 }
90
91 stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
92 if err != nil && !errors.Is(err, os.ErrNotExist) {
93 return err
94 }
95
96 stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
97 if err != nil && !errors.Is(err, os.ErrNotExist) {
98 return err
99 }
100
101 stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
102 if err != nil && !errors.Is(err, os.ErrNotExist) {
103 return err
104 }
105
106 stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
107 if err != nil && !errors.Is(err, os.ErrNotExist) {
108 return err
109 }
110
111 stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
112 if err != nil && !errors.Is(err, os.ErrNotExist) {
113 return err
114 }
115
116 stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
117 if err != nil && !errors.Is(err, os.ErrNotExist) {
118 return err
119 }
120
121 stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
122 if err != nil && !errors.Is(err, os.ErrNotExist) {
123 return err
124 }
125
126 stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
127 if err != nil && !errors.Is(err, os.ErrNotExist) {
128 return err
129 }
130
131 stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
132 if err != nil && !errors.Is(err, os.ErrNotExist) {
133 return err
134 }
135
136 stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
137 if err != nil && !errors.Is(err, os.ErrNotExist) {
138 return err
139 }
140
141 return nil
142 }
143
144 func (s *CpusetGroup) ApplyDir(dir string, r *configs.Resources, pid int) error {
145
146
147 if dir == "" {
148 return nil
149 }
150
151
152
153 if err := cpusetEnsureParent(filepath.Dir(dir)); err != nil {
154 return err
155 }
156 if err := os.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) {
157 return err
158 }
159
160
161
162
163
164
165
166 if err := s.ensureCpusAndMems(dir, r); err != nil {
167 return err
168 }
169
170
171 return cgroups.WriteCgroupProc(dir, pid)
172 }
173
174 func getCpusetSubsystemSettings(parent string) (cpus, mems string, err error) {
175 if cpus, err = cgroups.ReadFile(parent, "cpuset.cpus"); err != nil {
176 return
177 }
178 if mems, err = cgroups.ReadFile(parent, "cpuset.mems"); err != nil {
179 return
180 }
181 return cpus, mems, nil
182 }
183
184
185
186
187
188 func cpusetEnsureParent(current string) error {
189 var st unix.Statfs_t
190
191 parent := filepath.Dir(current)
192 err := unix.Statfs(parent, &st)
193 if err == nil && st.Type != unix.CGROUP_SUPER_MAGIC {
194 return nil
195 }
196
197
198 if err != nil && err != unix.ENOENT {
199 return &os.PathError{Op: "statfs", Path: parent, Err: err}
200 }
201
202 if err := cpusetEnsureParent(parent); err != nil {
203 return err
204 }
205 if err := os.Mkdir(current, 0o755); err != nil && !os.IsExist(err) {
206 return err
207 }
208 return cpusetCopyIfNeeded(current, parent)
209 }
210
211
212
213 func cpusetCopyIfNeeded(current, parent string) error {
214 currentCpus, currentMems, err := getCpusetSubsystemSettings(current)
215 if err != nil {
216 return err
217 }
218 parentCpus, parentMems, err := getCpusetSubsystemSettings(parent)
219 if err != nil {
220 return err
221 }
222
223 if isEmptyCpuset(currentCpus) {
224 if err := cgroups.WriteFile(current, "cpuset.cpus", parentCpus); err != nil {
225 return err
226 }
227 }
228 if isEmptyCpuset(currentMems) {
229 if err := cgroups.WriteFile(current, "cpuset.mems", parentMems); err != nil {
230 return err
231 }
232 }
233 return nil
234 }
235
236 func isEmptyCpuset(str string) bool {
237 return str == "" || str == "\n"
238 }
239
240 func (s *CpusetGroup) ensureCpusAndMems(path string, r *configs.Resources) error {
241 if err := s.Set(path, r); err != nil {
242 return err
243 }
244 return cpusetCopyIfNeeded(path, filepath.Dir(path))
245 }
246
View as plain text