• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 16280725298

14 Jul 2025 08:16PM UTC coverage: 72.166% (-0.006%) from 72.172%
16280725298

push

github

web-flow
Two fixlets for coverage test (#38183)

302135 of 418667 relevant lines covered (72.17%)

773261.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.73
/src/shared/cgroup-setup.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <linux/magic.h>
4
#include <unistd.h>
5

6
#include "cgroup-setup.h"
7
#include "cgroup-util.h"
8
#include "errno-util.h"
9
#include "fd-util.h"
10
#include "fileio.h"
11
#include "format-util.h"
12
#include "fs-util.h"
13
#include "log.h"
14
#include "mkdir.h"
15
#include "parse-util.h"
16
#include "path-util.h"
17
#include "process-util.h"
18
#include "recurse-dir.h"
19
#include "set.h"
20
#include "stat-util.h"
21
#include "stdio-util.h"
22
#include "string-util.h"
23
#include "user-util.h"
24

25
int cg_weight_parse(const char *s, uint64_t *ret) {
567✔
26
        uint64_t u;
567✔
27
        int r;
567✔
28

29
        assert(s);
567✔
30
        assert(ret);
567✔
31

32
        if (isempty(s)) {
567✔
33
                *ret = CGROUP_WEIGHT_INVALID;
×
34
                return 0;
×
35
        }
36

37
        r = safe_atou64(s, &u);
567✔
38
        if (r < 0)
567✔
39
                return r;
40

41
        if (u < CGROUP_WEIGHT_MIN || u > CGROUP_WEIGHT_MAX)
567✔
42
                return -ERANGE;
43

44
        *ret = u;
567✔
45
        return 0;
567✔
46
}
47

48
int cg_cpu_weight_parse(const char *s, uint64_t *ret) {
563✔
49
        assert(s);
563✔
50
        assert(ret);
563✔
51

52
        if (streq(s, "idle"))
563✔
53
                return *ret = CGROUP_WEIGHT_IDLE;
×
54

55
        return cg_weight_parse(s, ret);
563✔
56
}
57

58
static int trim_cb(
126,647✔
59
                RecurseDirEvent event,
60
                const char *path,
61
                int dir_fd,
62
                int inode_fd,
63
                const struct dirent *de,
64
                const struct statx *sx,
65
                void *userdata) {
66

67
        /* Failures to delete inner cgroup we ignore (but debug log in case error code is unexpected) */
68
        if (event == RECURSE_DIR_LEAVE &&
126,647✔
69
            de->d_type == DT_DIR &&
682✔
70
            unlinkat(dir_fd, de->d_name, AT_REMOVEDIR) < 0 &&
341✔
71
            !IN_SET(errno, ENOENT, ENOTEMPTY, EBUSY))
259✔
72
                log_debug_errno(errno, "Failed to trim inner cgroup %s, ignoring: %m", path);
1✔
73

74
        return RECURSE_DIR_CONTINUE;
126,647✔
75
}
76

77
int cg_trim(const char *path, bool delete_root) {
2,523✔
78
        _cleanup_free_ char *fs = NULL;
2,523✔
79
        int r;
2,523✔
80

81
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
2,523✔
82
        if (r < 0)
2,523✔
83
                return r;
84

85
        r = recurse_dir_at(
2,523✔
86
                        AT_FDCWD,
87
                        fs,
88
                        /* statx_mask = */ 0,
89
                        /* n_depth_max = */ UINT_MAX,
90
                        RECURSE_DIR_ENSURE_TYPE,
91
                        trim_cb,
92
                        /* userdata = */ NULL);
93
        if (r == -ENOENT) /* non-existing is the ultimate trimming, hence no error */
2,523✔
94
                r = 0;
95
        else if (r < 0)
2,521✔
96
                log_debug_errno(r, "Failed to trim subcgroups of '%s': %m", path);
×
97

98
        /* If we shall delete the top-level cgroup, then propagate the failure to do so (except if it is
99
         * already gone anyway). Also, let's debug log about this failure, except if the error code is an
100
         * expected one. */
101
        if (delete_root && !empty_or_root(path) &&
4,848✔
102
            rmdir(fs) < 0 && errno != ENOENT) {
2,329✔
103
                if (!IN_SET(errno, ENOTEMPTY, EBUSY))
2✔
104
                        log_debug_errno(errno, "Failed to trim cgroup '%s': %m", path);
×
105
                RET_GATHER(r, -errno);
2✔
106
        }
107

108
        return r;
109
}
110

111
/* Create a cgroup in the hierarchy of controller.
112
 * Returns 0 if the group already existed, 1 on success, negative otherwise.
113
 */
114
int cg_create(const char *path) {
6,015✔
115
        _cleanup_free_ char *fs = NULL;
6,015✔
116
        int r;
6,015✔
117

118
        r = cg_get_path_and_check(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
6,015✔
119
        if (r < 0)
6,015✔
120
                return r;
121

122
        r = mkdir_parents(fs, 0755);
6,015✔
123
        if (r < 0)
6,015✔
124
                return r;
125

126
        r = RET_NERRNO(mkdir(fs, 0755));
6,015✔
127
        if (r == -EEXIST)
2,673✔
128
                return 0;
129
        if (r < 0)
3,342✔
130
                return r;
×
131

132
        return 1;
133
}
134

135
int cg_attach(const char *path, pid_t pid) {
12,430✔
136
        _cleanup_free_ char *fs = NULL;
12,430✔
137
        char c[DECIMAL_STR_MAX(pid_t) + 2];
12,430✔
138
        int r;
12,430✔
139

140
        assert(path);
12,430✔
141
        assert(pid >= 0);
12,430✔
142

143
        r = cg_get_path_and_check(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.procs", &fs);
12,430✔
144
        if (r < 0)
12,430✔
145
                return r;
146

147
        if (pid == 0)
12,430✔
148
                pid = getpid_cached();
12,261✔
149

150
        xsprintf(c, PID_FMT "\n", pid);
12,430✔
151

152
        r = write_string_file(fs, c, WRITE_STRING_FILE_DISABLE_BUFFER);
12,430✔
153
        if (r == -EOPNOTSUPP && cg_is_threaded(path) > 0)
12,430✔
154
                /* When the threaded mode is used, we cannot read/write the file. Let's return recognizable error. */
155
                return -EUCLEAN;
156
        if (r < 0)
12,430✔
157
                return r;
3✔
158

159
        return 0;
160
}
161

162
int cg_fd_attach(int fd, pid_t pid) {
6✔
163
        char c[DECIMAL_STR_MAX(pid_t) + 2];
6✔
164

165
        assert(fd >= 0);
6✔
166
        assert(pid >= 0);
6✔
167

168
        if (pid == 0)
6✔
169
                pid = getpid_cached();
×
170

171
        xsprintf(c, PID_FMT "\n", pid);
6✔
172

173
        return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER);
6✔
174
}
175

176
int cg_create_and_attach(const char *path, pid_t pid) {
447✔
177
        int r, q;
447✔
178

179
        /* This does not remove the cgroup on failure */
180

181
        assert(pid >= 0);
447✔
182

183
        r = cg_create(path);
447✔
184
        if (r < 0)
447✔
185
                return r;
186

187
        q = cg_attach(path, pid);
447✔
188
        if (q < 0)
447✔
189
                return q;
×
190

191
        return r;
192
}
193

194
int cg_set_access(
684✔
195
                const char *path,
196
                uid_t uid,
197
                gid_t gid) {
198

199
        static const struct {
684✔
200
                const char *name;
201
                bool fatal;
202
        } attributes[] = {
203
                { "cgroup.procs",           true  },
204
                { "cgroup.subtree_control", true  },
205
                { "cgroup.threads",         false },
206
                { "memory.oom.group",       false },
207
                { "memory.reclaim",         false },
208
        };
209

210
        _cleanup_free_ char *fs = NULL;
684✔
211
        int r;
684✔
212

213
        assert(path);
684✔
214

215
        if (uid == UID_INVALID && gid == GID_INVALID)
684✔
216
                return 0;
217

218
        /* Configure access to the cgroup itself */
219
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
194✔
220
        if (r < 0)
194✔
221
                return r;
222

223
        r = chmod_and_chown(fs, 0755, uid, gid);
194✔
224
        if (r < 0)
194✔
225
                return r;
226

227
        /* Configure access to the cgroup's attributes */
228
        FOREACH_ELEMENT(i, attributes) {
1,164✔
229
                _cleanup_free_ char *a = path_join(fs, i->name);
1,940✔
230
                if (!a)
970✔
231
                        return -ENOMEM;
232

233
                r = chmod_and_chown(a, 0644, uid, gid);
970✔
234
                if (r < 0) {
970✔
235
                        if (i->fatal)
×
236
                                return r;
237

238
                        log_debug_errno(r, "Failed to set access on cgroup %s, ignoring: %m", a);
970✔
239
                }
240
        }
241

242
        return 0;
243
}
244

245
struct access_callback_data {
246
        uid_t uid;
247
        gid_t gid;
248
        int error;
249
};
250

251
static int access_callback(
3,622✔
252
                RecurseDirEvent event,
253
                const char *path,
254
                int dir_fd,
255
                int inode_fd,
256
                const struct dirent *de,
257
                const struct statx *sx,
258
                void *userdata) {
259

260
        if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
3,622✔
261
                return RECURSE_DIR_CONTINUE;
262

263
        struct access_callback_data *d = ASSERT_PTR(userdata);
3,436✔
264

265
        assert(path);
3,436✔
266
        assert(inode_fd >= 0);
3,436✔
267

268
        if (fchownat(inode_fd, "", d->uid, d->gid, AT_EMPTY_PATH) < 0)
3,436✔
269
                RET_GATHER(d->error, log_debug_errno(errno, "Failed to change ownership of '%s', ignoring: %m", path));
×
270

271
        return RECURSE_DIR_CONTINUE;
272
}
273

274
int cg_set_access_recursive(
339✔
275
                const char *path,
276
                uid_t uid,
277
                gid_t gid) {
278

279
        _cleanup_close_ int fd = -EBADF;
339✔
280
        _cleanup_free_ char *fs = NULL;
339✔
281
        int r;
339✔
282

283
        assert(path);
339✔
284

285
        /* A recursive version of cg_set_access(). But note that this one changes ownership of *all* files,
286
         * not just the allowlist that cg_set_access() uses. Use cg_set_access() on the cgroup you want to
287
         * delegate, and cg_set_access_recursive() for any subcgroups you might want to create below it. */
288

289
        if (!uid_is_valid(uid) && !gid_is_valid(gid))
492✔
290
                return 0;
291

292
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
186✔
293
        if (r < 0)
186✔
294
                return r;
295

296
        fd = open(fs, O_DIRECTORY|O_CLOEXEC);
186✔
297
        if (fd < 0)
186✔
298
                return -errno;
×
299

300
        struct access_callback_data d = {
186✔
301
                .uid = uid,
302
                .gid = gid,
303
        };
304

305
        r = recurse_dir(fd,
186✔
306
                        fs,
307
                        /* statx_mask= */ 0,
308
                        /* n_depth_max= */ UINT_MAX,
309
                        RECURSE_DIR_SAME_MOUNT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL,
310
                        access_callback,
311
                        &d);
312
        if (r < 0)
186✔
313
                return r;
314

315
        assert(d.error <= 0);
186✔
316
        return d.error;
317
}
318

319
int cg_migrate(
257✔
320
                const char *from,
321
                const char *to,
322
                CGroupFlags flags) {
323

324
        _cleanup_set_free_ Set *s = NULL;
257✔
325
        bool done;
257✔
326
        int r, ret = 0;
257✔
327

328
        assert(from);
257✔
329
        assert(to);
257✔
330

331
        do {
257✔
332
                _cleanup_fclose_ FILE *f = NULL;
257✔
333
                pid_t pid;
257✔
334

335
                done = true;
257✔
336

337
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, from, &f);
257✔
338
                if (r < 0)
257✔
339
                        return RET_GATHER(ret, r);
×
340

341
                while ((r = cg_read_pid(f, &pid, flags)) > 0) {
2,214✔
342
                        /* Throw an error if unmappable PIDs are in output, we can't migrate those. */
343
                        if (pid == 0)
1,957✔
344
                                return -EREMOTE;
345

346
                        /* This might do weird stuff if we aren't a single-threaded program. However, we
347
                         * luckily know we are. */
348
                        if (FLAGS_SET(flags, CGROUP_IGNORE_SELF) && pid == getpid_cached())
1,957✔
349
                                continue;
×
350

351
                        if (set_contains(s, PID_TO_PTR(pid)))
1,957✔
352
                                continue;
×
353

354
                        if (pid_is_kernel_thread(pid) > 0)
1,957✔
355
                                continue;
1,957✔
356

357
                        r = cg_attach(to, pid);
×
358
                        if (r < 0) {
×
359
                                if (r != -ESRCH)
×
360
                                        RET_GATHER(ret, r);
×
361
                        } else if (ret == 0)
×
362
                                ret = 1;
×
363

364
                        done = false;
×
365

366
                        r = set_ensure_put(&s, /* hash_ops = */ NULL, PID_TO_PTR(pid));
×
367
                        if (r < 0)
×
368
                                return RET_GATHER(ret, r);
×
369
                }
370
                if (r == -ENODEV)
257✔
371
                        continue;
×
372
                if (r < 0)
257✔
373
                        return RET_GATHER(ret, r);
×
374
        } while (!done);
257✔
375

376
        return ret;
377
}
378

379
int cg_enable(
5,266✔
380
                CGroupMask supported,
381
                CGroupMask mask,
382
                const char *p,
383
                CGroupMask *ret_result_mask) {
384

385
        _cleanup_fclose_ FILE *f = NULL;
5,266✔
386
        _cleanup_free_ char *fs = NULL;
5,266✔
387
        CGroupController c;
5,266✔
388
        CGroupMask ret = 0;
5,266✔
389
        int r;
5,266✔
390

391
        assert(p);
5,266✔
392

393
        if (supported == 0) {
5,266✔
394
                if (ret_result_mask)
×
395
                        *ret_result_mask = 0;
×
396
                return 0;
×
397
        }
398

399
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs);
5,266✔
400
        if (r < 0)
5,266✔
401
                return r;
402

403
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
73,724✔
404
                CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
68,458✔
405
                const char *n;
68,458✔
406

407
                if (!FLAGS_SET(CGROUP_MASK_V2, bit))
68,458✔
408
                        continue;
42,128✔
409

410
                if (!FLAGS_SET(supported, bit))
26,330✔
411
                        continue;
3,401✔
412

413
                n = cgroup_controller_to_string(c);
22,929✔
414
                {
22,929✔
415
                        char s[1 + strlen(n) + 1];
22,929✔
416

417
                        s[0] = FLAGS_SET(mask, bit) ? '+' : '-';
22,929✔
418
                        strcpy(s + 1, n);
22,929✔
419

420
                        if (!f) {
22,929✔
421
                                f = fopen(fs, "we");
5,243✔
422
                                if (!f)
5,243✔
423
                                        return log_debug_errno(errno, "Failed to open cgroup.subtree_control file of %s: %m", p);
×
424
                        }
425

426
                        r = write_string_stream(f, s, WRITE_STRING_FILE_DISABLE_BUFFER);
22,929✔
427
                        if (r < 0) {
22,929✔
428
                                log_debug_errno(r, "Failed to %s controller %s for %s (%s): %m",
12✔
429
                                                FLAGS_SET(mask, bit) ? "enable" : "disable", n, p, fs);
430
                                clearerr(f);
12✔
431

432
                                /* If we can't turn off a controller, leave it on in the reported resulting mask. This
433
                                 * happens for example when we attempt to turn off a controller up in the tree that is
434
                                 * used down in the tree. */
435
                                if (!FLAGS_SET(mask, bit) && r == -EBUSY) /* You might wonder why we check for EBUSY
12✔
436
                                                                           * only here, and not follow the same logic
437
                                                                           * for other errors such as EINVAL or
438
                                                                           * EOPNOTSUPP or anything else. That's
439
                                                                           * because EBUSY indicates that the
440
                                                                           * controllers is currently enabled and
441
                                                                           * cannot be disabled because something down
442
                                                                           * the hierarchy is still using it. Any other
443
                                                                           * error most likely means something like "I
444
                                                                           * never heard of this controller" or
445
                                                                           * similar. In the former case it's hence
446
                                                                           * safe to assume the controller is still on
447
                                                                           * after the failed operation, while in the
448
                                                                           * latter case it's safer to assume the
449
                                                                           * controller is unknown and hence certainly
450
                                                                           * not enabled. */
451
                                        ret |= bit;
×
452
                        } else {
453
                                /* Otherwise, if we managed to turn on a controller, set the bit reflecting that. */
454
                                if (FLAGS_SET(mask, bit))
22,917✔
455
                                        ret |= bit;
4,911✔
456
                        }
457
                }
458
        }
459

460
        /* Let's return the precise set of controllers now enabled for the cgroup. */
461
        if (ret_result_mask)
5,266✔
462
                *ret_result_mask = ret;
5,110✔
463

464
        return 0;
465
}
466

467
int cg_has_legacy(void) {
732✔
468
        struct statfs fs;
732✔
469

470
        /* Checks if any legacy controller/hierarchy is mounted. */
471

472
        if (statfs("/sys/fs/cgroup/", &fs) < 0) {
732✔
473
                if (errno == ENOENT) /* sysfs not mounted? */
×
474
                        return false;
732✔
475

476
                return log_error_errno(errno, "Failed to statfs /sys/fs/cgroup/: %m");
×
477
        }
478

479
        if (is_fs_type(&fs, CGROUP2_SUPER_MAGIC) ||
732✔
480
            is_fs_type(&fs, SYSFS_MAGIC)) /* not mounted yet */
×
481
                return false;
482

483
        if (is_fs_type(&fs, TMPFS_MAGIC)) {
×
484
                log_info("Found tmpfs on /sys/fs/cgroup/, assuming legacy hierarchy.");
×
485
                return true;
×
486
        }
487

488
        return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
489
                               "Unknown filesystem type %llx mounted on /sys/fs/cgroup/.",
490
                               (unsigned long long) fs.f_type);
491
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc