]>
Commit | Line | Data |
---|---|---|
9dd11ed0 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2021 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
5ae21aa1 | 21 | #include <dirent.h> |
9dd11ed0 MT |
22 | #include <errno.h> |
23 | #include <linux/limits.h> | |
24 | #include <linux/magic.h> | |
1b41d3b1 | 25 | #include <signal.h> |
9dd11ed0 MT |
26 | #include <stdlib.h> |
27 | #include <string.h> | |
28 | #include <sys/types.h> | |
29 | #include <sys/vfs.h> | |
d5256224 | 30 | #include <time.h> |
9dd11ed0 MT |
31 | |
32 | #include <pakfire/cgroup.h> | |
33 | #include <pakfire/logging.h> | |
34 | #include <pakfire/types.h> | |
35 | #include <pakfire/util.h> | |
36 | ||
37 | /* | |
38 | We expect this to be a cgroupv2 file system | |
39 | */ | |
40 | #define CGROUP_ROOT "/sys/fs/cgroup" | |
41 | ||
42 | // Cache whether cgroups are supported on this system | |
43 | static int pakfire_cgroups_supported = -1; | |
44 | ||
45 | static const char* cgroup_controllers[] = { | |
46 | "cpu", | |
47 | "memory", | |
48 | NULL, | |
49 | }; | |
50 | ||
51 | /* | |
52 | Returns the name of the parent group | |
53 | */ | |
54 | static char* pakfire_cgroup_parent_name(const char* group) { | |
55 | if (!group || !*group) | |
56 | return NULL; | |
57 | ||
58 | // Find the last / in group | |
59 | char* slash = strrchr(group, '/'); | |
60 | ||
61 | // If nothing was found, the next level up is "root" | |
62 | if (!slash) | |
63 | return strdup(""); | |
64 | ||
65 | size_t length = slash - group + 1; | |
66 | ||
67 | // Allocate the parent group name | |
68 | char* parent = malloc(length + 1); | |
69 | if (!parent) | |
70 | return NULL; | |
71 | ||
72 | // Write everything up to "slash" which snprintf will replace by NUL | |
73 | snprintf(parent, length, "%s", group); | |
74 | ||
75 | return parent; | |
76 | } | |
77 | ||
78 | static int pakfire_cgroup_supported(Pakfire pakfire) { | |
79 | if (pakfire_cgroups_supported < 0) { | |
80 | struct statfs fs; | |
81 | ||
82 | int r = statfs(CGROUP_ROOT, &fs); | |
83 | if (r == 0) { | |
84 | // Check if this is a cgroupv2 file system | |
85 | if (fs.f_type == CGROUP2_SUPER_MAGIC) | |
86 | pakfire_cgroups_supported = 1; | |
87 | else { | |
88 | ERROR(pakfire, "%s is not a cgroupv2 hierarchy\n", CGROUP_ROOT); | |
89 | pakfire_cgroups_supported = 0; | |
90 | } | |
91 | } else if (r < 0) { | |
92 | ERROR(pakfire, "Could not stat %s: %s\n", CGROUP_ROOT, strerror(errno)); | |
93 | pakfire_cgroups_supported = 0; | |
94 | } | |
95 | } | |
96 | ||
97 | return pakfire_cgroups_supported; | |
98 | } | |
99 | ||
100 | static int pakfire_cgroup_make_path(Pakfire pakfire, char* path, size_t length, | |
101 | const char* subgroup, const char* file) { | |
102 | // Store up to where we have written and how much space is left | |
103 | char* p = path; | |
104 | size_t l = length; | |
105 | ||
106 | // Write root | |
107 | size_t bytes_written = snprintf(p, l, "%s", CGROUP_ROOT); | |
108 | if (bytes_written >= l) | |
109 | return -1; | |
110 | ||
111 | p += bytes_written; | |
112 | l -= bytes_written; | |
113 | ||
114 | // Append subgroup | |
115 | if (subgroup) { | |
116 | bytes_written = snprintf(p, l, "/%s", subgroup); | |
117 | if (bytes_written >= l) | |
118 | return -1; | |
119 | ||
120 | p += bytes_written; | |
121 | l -= bytes_written; | |
122 | } | |
123 | ||
124 | // Append file | |
125 | if (file) { | |
126 | bytes_written = snprintf(p, l, "/%s", file); | |
127 | if (bytes_written >= l) | |
128 | return -1; | |
129 | ||
130 | p += bytes_written; | |
131 | l -= bytes_written; | |
132 | } | |
133 | ||
134 | // Return total bytes written | |
135 | return length - l; | |
136 | } | |
137 | ||
138 | static FILE* pakfire_cgroup_fopen(Pakfire pakfire, | |
139 | const char* group, const char* file, const char* mode) { | |
140 | char path[PATH_MAX]; | |
141 | ||
142 | int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, file); | |
143 | if (r < 0) | |
144 | return NULL; | |
145 | ||
146 | FILE* f = fopen(path, mode); | |
147 | if (!f) { | |
148 | ERROR(pakfire, "Could not open %s: %s\n", path, strerror(errno)); | |
149 | return NULL; | |
150 | } | |
151 | ||
152 | return f; | |
153 | } | |
154 | ||
305de320 MT |
155 | static int pakfire_cgroup_fprintf(Pakfire pakfire, |
156 | const char* group, const char* file, const char* format, ...) { | |
ac149196 MT |
157 | char buffer[64]; |
158 | size_t length; | |
9dd11ed0 | 159 | int r; |
305de320 | 160 | va_list args; |
9dd11ed0 | 161 | |
305de320 | 162 | FILE* f = pakfire_cgroup_fopen(pakfire, group, file, "w"); |
9dd11ed0 MT |
163 | if (!f) |
164 | return 1; | |
165 | ||
ac149196 | 166 | // Format what we have to write |
305de320 | 167 | va_start(args, format); |
ac149196 | 168 | length = vsnprintf(buffer, sizeof(buffer) - 1, format, args); |
305de320 MT |
169 | va_end(args); |
170 | ||
ac149196 MT |
171 | // Use write(2) instead of fprintf/fwrite because we want to know |
172 | // if the operation was successful. | |
173 | r = write(fileno(f), buffer, length); | |
174 | ||
305de320 MT |
175 | fclose(f); |
176 | ||
177 | return r; | |
178 | } | |
179 | ||
180 | static int pakfire_cgroup_enable_controller(Pakfire pakfire, | |
181 | const char* group, const char* controller) { | |
9dd11ed0 | 182 | // Enable controller |
305de320 MT |
183 | int r = pakfire_cgroup_fprintf(pakfire, group, "cgroup.subtree_control", |
184 | "+%s", controller); | |
9dd11ed0 MT |
185 | |
186 | // fprintf might set errno when there was a problem, although the write itself was ok | |
ac149196 | 187 | if (r < 0) { |
9dd11ed0 MT |
188 | // The parent group does not seem to have this controller enabled |
189 | if (errno == ENOENT) { | |
190 | char* parent = pakfire_cgroup_parent_name(group); | |
191 | if (!parent) | |
305de320 | 192 | return 1; |
9dd11ed0 MT |
193 | |
194 | // Try to enable this on the parent level | |
195 | r = pakfire_cgroup_enable_controller(pakfire, parent, controller); | |
196 | free(parent); | |
197 | ||
198 | // If this failed, we fail | |
199 | if (r) | |
305de320 | 200 | return r; |
9dd11ed0 MT |
201 | |
202 | // Otherwise we try again | |
305de320 | 203 | return pakfire_cgroup_enable_controller(pakfire, group, controller); |
9dd11ed0 MT |
204 | } |
205 | } | |
206 | ||
305de320 | 207 | return 0; |
9dd11ed0 MT |
208 | } |
209 | ||
210 | static int pakfire_cgroup_enable_controllers(Pakfire pakfire, | |
211 | const char* group, const char** controllers) { | |
212 | int r; | |
213 | ||
214 | // Enable all controllers | |
215 | for (const char** controller = controllers; *controller; controller++) { | |
216 | r = pakfire_cgroup_enable_controller(pakfire, group, *controller); | |
217 | if (r) | |
218 | return r; | |
219 | } | |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
224 | int pakfire_cgroup_create(Pakfire pakfire, const char* group) { | |
225 | int supported = pakfire_cgroup_supported(pakfire); | |
226 | if (!supported) | |
227 | return 1; | |
228 | ||
229 | // Ensure that parent groups exist | |
230 | char* parent = pakfire_cgroup_parent_name(group); | |
231 | if (parent) { | |
232 | int r = pakfire_cgroup_create(pakfire, parent); | |
69cfa22d MT |
233 | if (r) { |
234 | free(parent); | |
235 | return r; | |
236 | } | |
237 | ||
238 | // Enable default controllers in all parent groups | |
239 | r = pakfire_cgroup_enable_controllers(pakfire, parent, cgroup_controllers); | |
240 | if (r) { | |
241 | free(parent); | |
242 | return r; | |
243 | } | |
244 | ||
9dd11ed0 | 245 | free(parent); |
9dd11ed0 MT |
246 | } |
247 | ||
248 | // Make path | |
249 | char path[PATH_MAX]; | |
250 | int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, NULL); | |
251 | if (r < 0) | |
252 | return r; | |
253 | ||
254 | // Create group | |
255 | r = pakfire_mkdir(path, 0755); | |
256 | if (r) { | |
257 | switch (errno) { | |
258 | // The group already exists | |
259 | case EEXIST: | |
260 | return 0; | |
261 | ||
262 | default: | |
263 | ERROR(pakfire, "Could not create cgroup %s: %s\n", group, strerror(errno)); | |
264 | return r; | |
265 | } | |
266 | } | |
267 | ||
268 | DEBUG(pakfire, "Created cgroup %s\n", group); | |
269 | ||
9dd11ed0 MT |
270 | return 0; |
271 | } | |
272 | ||
273 | int pakfire_cgroup_destroy(Pakfire pakfire, const char* group) { | |
274 | // Never attempt to destroy root | |
275 | if (!*group) | |
276 | return EINVAL; | |
277 | ||
278 | char path[PATH_MAX]; | |
279 | ||
280 | int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, NULL); | |
281 | if (r < 0) | |
282 | return r; | |
283 | ||
284 | // Remove the directory | |
285 | r = rmdir(path); | |
286 | if (r) { | |
287 | ERROR(pakfire, "Could not destroy cgroup %s: %s\n", group, strerror(errno)); | |
288 | return r; | |
289 | } | |
290 | ||
291 | return 0; | |
292 | } | |
4630031c | 293 | |
5ae21aa1 MT |
294 | DIR* pakfire_cgroup_opendir(Pakfire pakfire, const char* group) { |
295 | // Make path | |
296 | char path[PATH_MAX]; | |
297 | int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, NULL); | |
298 | if (r < 0) | |
299 | return NULL; | |
300 | ||
301 | return opendir(path); | |
302 | } | |
303 | ||
4630031c MT |
304 | int pakfire_cgroup_attach(Pakfire pakfire, const char* group, pid_t pid) { |
305 | int r = pakfire_cgroup_fprintf(pakfire, group, "cgroup.procs", "%d", pid); | |
306 | if (r < 0) { | |
307 | ERROR(pakfire, "Could not attach process %d to cgroup %s: %s\n", | |
308 | pid, group, strerror(errno)); | |
309 | return r; | |
310 | } | |
311 | ||
312 | DEBUG(pakfire, "Attached process %d to cgroup %s\n", pid, group); | |
313 | return 0; | |
314 | } | |
315 | ||
316 | int pakfire_cgroup_detach(Pakfire pakfire, const char* group, pid_t pid) { | |
317 | char* parent = pakfire_cgroup_parent_name(group); | |
318 | if (!parent) | |
319 | return EINVAL; | |
320 | ||
321 | while (parent) { | |
322 | int r = pakfire_cgroup_attach(pakfire, parent, pid); | |
323 | ||
324 | // Break on success | |
325 | if (r == 0) { | |
326 | free(parent); | |
327 | return 0; | |
328 | } | |
329 | ||
330 | // Move on to the next parent group | |
331 | char* p = parent; | |
332 | parent = pakfire_cgroup_parent_name(p); | |
333 | free(p); | |
334 | } | |
335 | ||
336 | ERROR(pakfire, "Could not detach process %d from %s\n", pid, group); | |
337 | return 1; | |
338 | } | |
339 | ||
340 | static ssize_t pakfire_cgroup_procs_callback(Pakfire pakfire, const char* group, | |
1b41d3b1 | 341 | int (*func)(Pakfire pakfire, pid_t pid, void* data), void* data) { |
4630031c MT |
342 | FILE* f = pakfire_cgroup_fopen(pakfire, group, "cgroup.procs", "r"); |
343 | if (!f) | |
344 | return -1; | |
345 | ||
346 | ssize_t num_processes = 0; | |
347 | ||
348 | char* line = NULL; | |
349 | size_t l = 0; | |
350 | ||
351 | while (1) { | |
352 | ssize_t bytes_read = getline(&line, &l, f); | |
353 | if (bytes_read < 0) | |
354 | break; | |
355 | ||
356 | // Increment process counter | |
357 | num_processes++; | |
358 | ||
1b41d3b1 MT |
359 | // Process callback |
360 | if (func) { | |
361 | // Parse PID | |
362 | pid_t pid = strtol(line, NULL, 10); | |
363 | ||
364 | // Call callback function | |
365 | int r = func(pakfire, pid, data); | |
366 | if (r) { | |
367 | fclose(f); | |
368 | return -r; | |
369 | } | |
370 | } | |
4630031c MT |
371 | } |
372 | ||
373 | fclose(f); | |
374 | ||
375 | // Returns the number of processes | |
376 | return num_processes; | |
377 | } | |
378 | ||
379 | ssize_t pakfire_cgroup_num_processes(Pakfire pakfire, const char* group) { | |
1b41d3b1 MT |
380 | return pakfire_cgroup_procs_callback(pakfire, group, NULL, NULL); |
381 | } | |
382 | ||
383 | static int send_signal(Pakfire pakfire, pid_t pid, void* data) { | |
384 | int* signum = (int*)data; | |
385 | ||
386 | DEBUG(pakfire, "Sending signal %d to PID %d\n", *signum, pid); | |
387 | ||
388 | int r = kill(pid, *signum); | |
389 | if (r < 0 && errno != ESRCH) { | |
390 | ERROR(pakfire, "Could not send signal %d to PID %d: %s\n", | |
391 | *signum, pid, strerror(errno)); | |
392 | return r; | |
393 | } | |
394 | ||
395 | return 0; | |
396 | } | |
397 | ||
398 | int pakfire_cgroup_killall(Pakfire pakfire, const char* group) { | |
399 | DEBUG(pakfire, "Killing all processes in cgroup %s\n", group); | |
400 | int signum = SIGTERM; | |
401 | ||
402 | int count = 0; | |
403 | while (1) { | |
404 | // Kill all processes | |
405 | size_t num_procs = pakfire_cgroup_procs_callback(pakfire, group, | |
406 | send_signal, &signum); | |
407 | ||
408 | // If no processes are left, we are done | |
409 | if (!num_procs) | |
410 | return 0; | |
411 | ||
412 | DEBUG(pakfire, " %zu process(es) left\n", num_procs); | |
413 | ||
414 | // Use SIGKILL after 5 attempts with SIGTERM | |
415 | if (count++ > 5 && signum == SIGTERM) | |
416 | signum = SIGKILL; | |
417 | ||
418 | usleep(100000); | |
419 | } | |
420 | ||
421 | return 1; | |
4630031c | 422 | } |
d5256224 MT |
423 | |
424 | int pakfire_cgroup_cpustat(Pakfire pakfire, const char* group, | |
425 | struct pakfire_cgroup_cpustat* st) { | |
426 | FILE* f = pakfire_cgroup_fopen(pakfire, group, "cpu.stat", "r"); | |
427 | if (!f) | |
428 | return 1; | |
429 | ||
430 | const struct keyword { | |
431 | const char* keyword; | |
432 | struct timeval* value; | |
433 | } keywords[] = { | |
434 | { "usage_usec", &st->usage }, | |
435 | { "user_usec", &st->user }, | |
436 | { "system_usec", &st->system }, | |
437 | { NULL, NULL }, | |
438 | }; | |
439 | ||
440 | char* line = NULL; | |
441 | size_t l = 0; | |
442 | ||
443 | while (1) { | |
444 | ssize_t bytes_read = getline(&line, &l, f); | |
445 | if (bytes_read < 0) | |
446 | break; | |
447 | ||
d5256224 MT |
448 | for (const struct keyword* keyword = keywords; keyword->keyword; keyword++) { |
449 | if (pakfire_string_startswith(line, keyword->keyword)) { | |
450 | const char* p = line + strlen(keyword->keyword) + 1; | |
451 | ||
452 | unsigned long long v = strtoull(p, NULL, 10); | |
453 | ||
454 | // Set value | |
455 | keyword->value->tv_sec = v / 1000000; | |
456 | keyword->value->tv_usec = v % 1000000; | |
457 | } | |
458 | } | |
459 | } | |
460 | ||
461 | fclose(f); | |
462 | ||
463 | return 0; | |
464 | } |