]> git.ipfire.org Git - pakfire.git/blame - src/libpakfire/cgroup.c
execute: Fork new processes straight into their cgroup
[pakfire.git] / src / libpakfire / cgroup.c
CommitLineData
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
43static int pakfire_cgroups_supported = -1;
44
45static const char* cgroup_controllers[] = {
46 "cpu",
47 "memory",
48 NULL,
49};
50
51/*
52 Returns the name of the parent group
53*/
54static 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
78static 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
100static 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
138static 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
155static 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
180static 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
210static 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
224int 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
273int 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
294DIR* 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
304int 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
316int 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
340static 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
379ssize_t pakfire_cgroup_num_processes(Pakfire pakfire, const char* group) {
1b41d3b1
MT
380 return pakfire_cgroup_procs_callback(pakfire, group, NULL, NULL);
381}
382
383static 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
398int 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
424int 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}