]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <stdio.h> | |
4 | #include <syslog.h> | |
5 | #include <unistd.h> | |
6 | ||
7 | #include "alloc-util.h" | |
8 | #include "bitfield.h" | |
9 | #include "cpu-set-util.h" | |
10 | #include "extract-word.h" | |
11 | #include "hexdecoct.h" | |
12 | #include "log.h" | |
13 | #include "parse-util.h" | |
14 | #include "string-util.h" | |
15 | ||
16 | /* As of kernel 5.1, CONFIG_NR_CPUS can be set to 8192 on PowerPC */ | |
17 | #define CPU_SET_MAX_NCPU 8192 | |
18 | ||
19 | char* cpu_set_to_string(const CPUSet *c) { | |
20 | _cleanup_free_ char *str = NULL; | |
21 | ||
22 | assert(c); | |
23 | ||
24 | for (size_t i = 0; i < c->allocated * 8; i++) { | |
25 | if (!CPU_ISSET_S(i, c->allocated, c->set)) | |
26 | continue; | |
27 | ||
28 | if (strextendf_with_separator(&str, " ", "%zu", i) < 0) | |
29 | return NULL; | |
30 | } | |
31 | ||
32 | return TAKE_PTR(str) ?: strdup(""); | |
33 | } | |
34 | ||
35 | static int add_range(char **str, size_t start, size_t end) { | |
36 | assert(str); | |
37 | assert(start <= end); | |
38 | ||
39 | if (start == end) | |
40 | return strextendf_with_separator(str, " ", "%zu", start); | |
41 | ||
42 | return strextendf_with_separator(str, " ", "%zu-%zu", start, end); | |
43 | } | |
44 | ||
45 | char* cpu_set_to_range_string(const CPUSet *c) { | |
46 | _cleanup_free_ char *str = NULL; | |
47 | size_t start = 0, end; | |
48 | bool in_range = false; | |
49 | ||
50 | assert(c); | |
51 | ||
52 | for (size_t i = 0; i < c->allocated * 8; i++) { | |
53 | if (CPU_ISSET_S(i, c->allocated, c->set)) { | |
54 | if (in_range) | |
55 | end++; | |
56 | else { | |
57 | start = end = i; | |
58 | in_range = true; | |
59 | } | |
60 | continue; | |
61 | } | |
62 | ||
63 | if (in_range && add_range(&str, start, end) < 0) | |
64 | return NULL; | |
65 | ||
66 | in_range = false; | |
67 | } | |
68 | ||
69 | if (in_range && add_range(&str, start, end) < 0) | |
70 | return NULL; | |
71 | ||
72 | return TAKE_PTR(str) ?: strdup(""); | |
73 | } | |
74 | ||
75 | char* cpu_set_to_mask_string(const CPUSet *c) { | |
76 | _cleanup_free_ char *str = NULL; | |
77 | bool found_nonzero = false; | |
78 | int r; | |
79 | ||
80 | assert(c); | |
81 | ||
82 | /* Return CPU set in hexadecimal bitmap mask, e.g. | |
83 | * CPU 0 -> "1" | |
84 | * CPU 1 -> "2" | |
85 | * CPU 0,1 -> "3" | |
86 | * CPU 0-3 -> "f" | |
87 | * CPU 0-7 -> "ff" | |
88 | * CPU 4-7 -> "f0" | |
89 | * CPU 7 -> "80" | |
90 | * None -> "0" | |
91 | * | |
92 | * When there are more than 32 CPUs, separate every 32 CPUs by comma, e.g. | |
93 | * CPU 0-47 -> "ffff,ffffffff" | |
94 | * CPU 0-63 -> "ffffffff,ffffffff" | |
95 | * CPU 0-71 -> "ff,ffffffff,ffffffff" */ | |
96 | ||
97 | for (size_t i = c->allocated * 8; i > 0; ) { | |
98 | uint32_t m = 0; | |
99 | ||
100 | for (int j = (i % 32 ?: 32) - 1; j >= 0; j--) | |
101 | if (CPU_ISSET_S(--i, c->allocated, c->set)) | |
102 | SET_BIT(m, j); | |
103 | ||
104 | if (!found_nonzero) { | |
105 | if (m == 0) | |
106 | continue; | |
107 | ||
108 | r = strextendf_with_separator(&str, ",", "%" PRIx32, m); | |
109 | } else | |
110 | r = strextendf_with_separator(&str, ",", "%08" PRIx32, m); | |
111 | if (r < 0) | |
112 | return NULL; | |
113 | ||
114 | found_nonzero = true; | |
115 | } | |
116 | ||
117 | return TAKE_PTR(str) ?: strdup("0"); | |
118 | } | |
119 | ||
120 | void cpu_set_done(CPUSet *c) { | |
121 | assert(c); | |
122 | ||
123 | if (c->set) | |
124 | CPU_FREE(c->set); | |
125 | ||
126 | *c = (CPUSet) {}; | |
127 | } | |
128 | ||
129 | int cpu_set_realloc(CPUSet *c, size_t n) { | |
130 | assert(c); | |
131 | ||
132 | if (n > CPU_SET_MAX_NCPU) | |
133 | return -ERANGE; | |
134 | ||
135 | n = CPU_ALLOC_SIZE(n); | |
136 | if (n <= c->allocated) | |
137 | return 0; | |
138 | ||
139 | if (!GREEDY_REALLOC0(c->set, DIV_ROUND_UP(n, sizeof(cpu_set_t)))) | |
140 | return -ENOMEM; | |
141 | ||
142 | c->allocated = n; | |
143 | return 0; | |
144 | } | |
145 | ||
146 | int cpu_set_add(CPUSet *c, size_t i) { | |
147 | int r; | |
148 | ||
149 | assert(c); | |
150 | ||
151 | /* cpu_set_realloc() has similar check, but for avoiding overflow. */ | |
152 | if (i >= CPU_SET_MAX_NCPU) | |
153 | return -ERANGE; | |
154 | ||
155 | r = cpu_set_realloc(c, i + 1); | |
156 | if (r < 0) | |
157 | return r; | |
158 | ||
159 | CPU_SET_S(i, c->allocated, c->set); | |
160 | return 0; | |
161 | } | |
162 | ||
163 | int cpu_set_add_set(CPUSet *c, const CPUSet *src) { | |
164 | int r; | |
165 | ||
166 | assert(c); | |
167 | assert(src); | |
168 | ||
169 | r = cpu_set_realloc(c, src->allocated * 8); | |
170 | if (r < 0) | |
171 | return r; | |
172 | ||
173 | for (size_t i = 0; i < src->allocated * 8; i++) | |
174 | if (CPU_ISSET_S(i, src->allocated, src->set)) | |
175 | CPU_SET_S(i, c->allocated, c->set); | |
176 | ||
177 | return 1; | |
178 | } | |
179 | ||
180 | static int cpu_set_add_range(CPUSet *c, size_t start, size_t end) { | |
181 | int r; | |
182 | ||
183 | assert(c); | |
184 | assert(start <= end); | |
185 | ||
186 | /* cpu_set_realloc() has similar check, but for avoiding overflow. */ | |
187 | if (end >= CPU_SET_MAX_NCPU) | |
188 | return -ERANGE; | |
189 | ||
190 | r = cpu_set_realloc(c, end + 1); | |
191 | if (r < 0) | |
192 | return r; | |
193 | ||
194 | for (size_t i = start; i <= end; i++) | |
195 | CPU_SET_S(i, c->allocated, c->set); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | int cpu_set_add_all(CPUSet *c) { | |
201 | assert(c); | |
202 | ||
203 | long m = sysconf(_SC_NPROCESSORS_ONLN); | |
204 | if (m < 0) | |
205 | return -errno; | |
206 | if (m == 0) | |
207 | return -ENXIO; | |
208 | ||
209 | return cpu_set_add_range(c, 0, m - 1); | |
210 | } | |
211 | ||
212 | int config_parse_cpu_set( | |
213 | const char *unit, | |
214 | const char *filename, | |
215 | unsigned line, | |
216 | const char *section, | |
217 | unsigned section_line, | |
218 | const char *lvalue, | |
219 | int ltype, /* 0 when used as conf parser, 1 when used as usual parser */ | |
220 | const char *rvalue, | |
221 | void *data, | |
222 | void *userdata) { | |
223 | ||
224 | CPUSet *c = ASSERT_PTR(data); | |
225 | int r, level = ltype ? LOG_DEBUG : LOG_DEBUG; | |
226 | bool critical = ltype; | |
227 | ||
228 | assert(critical || lvalue); | |
229 | ||
230 | if (isempty(rvalue)) { | |
231 | cpu_set_done(c); | |
232 | return 1; | |
233 | } | |
234 | ||
235 | _cleanup_(cpu_set_done) CPUSet cpuset = {}; | |
236 | for (const char *p = rvalue;;) { | |
237 | _cleanup_free_ char *word = NULL; | |
238 | ||
239 | r = extract_first_word(&p, &word, WHITESPACE ",", EXTRACT_UNQUOTE); | |
240 | if (r == -ENOMEM) | |
241 | return log_oom_full(level); | |
242 | if (r < 0) { | |
243 | if (critical) | |
244 | return log_debug_errno(r, "Failed to parse CPU set: %s", rvalue); | |
245 | ||
246 | log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s= setting, ignoring assignment: %s", | |
247 | lvalue, rvalue); | |
248 | return 0; | |
249 | } | |
250 | if (r == 0) | |
251 | break; | |
252 | ||
253 | unsigned lower, upper; | |
254 | r = parse_range(word, &lower, &upper); | |
255 | if (r < 0) { | |
256 | if (critical) | |
257 | return log_debug_errno(r, "Failed to parse CPU range: %s", word); | |
258 | ||
259 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
260 | "Failed to parse CPU range, ignoring assignment: %s", word); | |
261 | continue; | |
262 | } | |
263 | ||
264 | if (lower > upper) { | |
265 | if (critical) | |
266 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), | |
267 | "Invalid CPU range (%u > %u): %s", | |
268 | lower, upper, word); | |
269 | ||
270 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
271 | "Invalid CPU range (%u > %u), ignoring assignment: %s", | |
272 | lower, upper, word); | |
273 | continue; | |
274 | } | |
275 | ||
276 | r = cpu_set_add_range(&cpuset, lower, upper); | |
277 | if (r == -ENOMEM) | |
278 | return log_oom_full(level); | |
279 | if (r < 0) { | |
280 | if (critical) | |
281 | return log_debug_errno(r, "Failed to set CPU(s) '%s': %m", word); | |
282 | ||
283 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
284 | "Failed to set CPU(s), ignoring assignment: %s", word); | |
285 | } | |
286 | } | |
287 | ||
288 | if (!c->set) { | |
289 | *c = TAKE_STRUCT(cpuset); | |
290 | return 1; | |
291 | } | |
292 | ||
293 | r = cpu_set_add_set(c, &cpuset); | |
294 | if (r == -ENOMEM) | |
295 | return log_oom_full(level); | |
296 | assert(r >= 0); | |
297 | ||
298 | return 1; | |
299 | } | |
300 | ||
301 | int parse_cpu_set(const char *s, CPUSet *ret) { | |
302 | _cleanup_(cpu_set_done) CPUSet c = {}; | |
303 | int r; | |
304 | ||
305 | assert(s); | |
306 | assert(ret); | |
307 | ||
308 | r = config_parse_cpu_set( | |
309 | /* unit = */ NULL, | |
310 | /* filename = */ NULL, | |
311 | /* line = */ 0, | |
312 | /* section = */ NULL, | |
313 | /* section_line = */ 0, | |
314 | /* lvalue = */ NULL, | |
315 | /* ltype = */ 1, | |
316 | /* rvalue = */ s, | |
317 | /* data = */ &c, | |
318 | /* userdata = */ NULL); | |
319 | if (r < 0) | |
320 | return r; | |
321 | ||
322 | *ret = TAKE_STRUCT(c); | |
323 | return 0; | |
324 | } | |
325 | ||
326 | int cpus_in_affinity_mask(void) { | |
327 | size_t n = 16; | |
328 | int r; | |
329 | ||
330 | for (;;) { | |
331 | cpu_set_t *c; | |
332 | ||
333 | c = CPU_ALLOC(n); | |
334 | if (!c) | |
335 | return -ENOMEM; | |
336 | ||
337 | if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) { | |
338 | int k; | |
339 | ||
340 | k = CPU_COUNT_S(CPU_ALLOC_SIZE(n), c); | |
341 | CPU_FREE(c); | |
342 | ||
343 | if (k <= 0) | |
344 | return -EINVAL; | |
345 | ||
346 | return k; | |
347 | } | |
348 | ||
349 | r = -errno; | |
350 | CPU_FREE(c); | |
351 | ||
352 | if (r != -EINVAL) | |
353 | return r; | |
354 | if (n > SIZE_MAX/2) | |
355 | return -ENOMEM; | |
356 | n *= 2; | |
357 | } | |
358 | } | |
359 | ||
360 | int cpu_set_to_dbus(const CPUSet *c, uint8_t **ret, size_t *ret_size) { | |
361 | assert(c); | |
362 | assert(ret); | |
363 | assert(ret_size); | |
364 | ||
365 | uint8_t *buf = new0(uint8_t, c->allocated); | |
366 | if (!buf) | |
367 | return -ENOMEM; | |
368 | ||
369 | for (size_t i = 0; i < c->allocated * 8; i++) | |
370 | if (CPU_ISSET_S(i, c->allocated, c->set)) | |
371 | SET_BIT(buf[i / 8], i % 8); | |
372 | ||
373 | *ret = buf; | |
374 | *ret_size = c->allocated; | |
375 | return 0; | |
376 | } | |
377 | ||
378 | int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *ret) { | |
379 | _cleanup_(cpu_set_done) CPUSet c = {}; | |
380 | int r; | |
381 | ||
382 | assert(bits || size == 0); | |
383 | assert(ret); | |
384 | ||
385 | r = cpu_set_realloc(&c, size * 8); | |
386 | if (r < 0) | |
387 | return r; | |
388 | ||
389 | for (size_t i = 0; i < size * 8; i++) | |
390 | if (BIT_SET(bits[i / 8], i % 8)) | |
391 | CPU_SET_S(i, c.allocated, c.set); | |
392 | ||
393 | *ret = TAKE_STRUCT(c); | |
394 | return 0; | |
395 | } |