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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 hits per line

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

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

3
#include <unistd.h>
4

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

22
int cg_weight_parse(const char *s, uint64_t *ret) {
531✔
23
        uint64_t u;
531✔
24
        int r;
531✔
25

26
        assert(s);
531✔
27
        assert(ret);
531✔
28

29
        if (isempty(s)) {
531✔
30
                *ret = CGROUP_WEIGHT_INVALID;
×
31
                return 0;
×
32
        }
33

34
        r = safe_atou64(s, &u);
531✔
35
        if (r < 0)
531✔
36
                return r;
37

38
        if (u < CGROUP_WEIGHT_MIN || u > CGROUP_WEIGHT_MAX)
531✔
39
                return -ERANGE;
40

41
        *ret = u;
531✔
42
        return 0;
531✔
43
}
44

45
int cg_cpu_weight_parse(const char *s, uint64_t *ret) {
529✔
46
        assert(s);
529✔
47
        assert(ret);
529✔
48

49
        if (streq(s, "idle"))
529✔
50
                return *ret = CGROUP_WEIGHT_IDLE;
×
51

52
        return cg_weight_parse(s, ret);
529✔
53
}
54

55
static int trim_cb(
116,892✔
56
                RecurseDirEvent event,
57
                const char *path,
58
                int dir_fd,
59
                int inode_fd,
60
                const struct dirent *de,
61
                const struct statx *sx,
62
                void *userdata) {
63

64
        /* Failures to delete inner cgroup we ignore (but debug log in case error code is unexpected) */
65
        if (event == RECURSE_DIR_LEAVE &&
116,892✔
66
            de->d_type == DT_DIR &&
592✔
67
            unlinkat(dir_fd, de->d_name, AT_REMOVEDIR) < 0 &&
296✔
68
            !IN_SET(errno, ENOENT, ENOTEMPTY, EBUSY))
236✔
69
                log_debug_errno(errno, "Failed to trim inner cgroup %s, ignoring: %m", path);
1✔
70

71
        return RECURSE_DIR_CONTINUE;
116,892✔
72
}
73

74
int cg_trim(const char *path, bool delete_root) {
6,229✔
75
        _cleanup_free_ char *fs = NULL;
6,229✔
76
        int r;
6,229✔
77

78
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
6,229✔
79
        if (r < 0)
6,229✔
80
                return r;
81

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

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

105
        return r;
106
}
107

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

115
        r = cg_get_path_and_check(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
4,391✔
116
        if (r < 0)
4,391✔
117
                return r;
118

119
        r = mkdir_parents(fs, 0755);
4,391✔
120
        if (r < 0)
4,391✔
121
                return r;
122

123
        r = RET_NERRNO(mkdir(fs, 0755));
4,391✔
124
        if (r == -EEXIST)
1,491✔
125
                return 0;
126
        if (r < 0)
2,900✔
127
                return r;
×
128

129
        return 1;
130
}
131

132
int cg_attach(const char *path, pid_t pid) {
11,919✔
133
        _cleanup_free_ char *fs = NULL;
11,919✔
134
        char c[DECIMAL_STR_MAX(pid_t) + 2];
11,919✔
135
        int r;
11,919✔
136

137
        assert(path);
11,919✔
138
        assert(pid >= 0);
11,919✔
139

140
        r = cg_get_path_and_check(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.procs", &fs);
11,919✔
141
        if (r < 0)
11,919✔
142
                return r;
143

144
        if (pid == 0)
11,919✔
145
                pid = getpid_cached();
11,760✔
146

147
        xsprintf(c, PID_FMT "\n", pid);
11,919✔
148

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

156
        return 0;
157
}
158

159
int cg_fd_attach(int fd, pid_t pid) {
7✔
160
        char c[DECIMAL_STR_MAX(pid_t) + 2];
7✔
161

162
        assert(fd >= 0);
7✔
163
        assert(pid >= 0);
7✔
164

165
        if (pid == 0)
7✔
166
                pid = getpid_cached();
×
167

168
        xsprintf(c, PID_FMT "\n", pid);
7✔
169

170
        return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER);
7✔
171
}
172

173
int cg_create_and_attach(const char *path, pid_t pid) {
410✔
174
        int r, q;
410✔
175

176
        /* This does not remove the cgroup on failure */
177

178
        assert(pid >= 0);
410✔
179

180
        r = cg_create(path);
410✔
181
        if (r < 0)
410✔
182
                return r;
183

184
        q = cg_attach(path, pid);
410✔
185
        if (q < 0)
410✔
186
                return q;
×
187

188
        return r;
189
}
190

191
int cg_set_access(
663✔
192
                const char *path,
193
                uid_t uid,
194
                gid_t gid) {
195

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

207
        _cleanup_free_ char *fs = NULL;
663✔
208
        int r;
663✔
209

210
        assert(path);
663✔
211

212
        if (uid == UID_INVALID && gid == GID_INVALID)
663✔
213
                return 0;
214

215
        /* Configure access to the cgroup itself */
216
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
185✔
217
        if (r < 0)
185✔
218
                return r;
219

220
        r = chmod_and_chown(fs, 0755, uid, gid);
185✔
221
        if (r < 0)
185✔
222
                return r;
223

224
        /* Configure access to the cgroup's attributes */
225
        FOREACH_ELEMENT(i, attributes) {
1,110✔
226
                _cleanup_free_ char *a = path_join(fs, i->name);
1,850✔
227
                if (!a)
925✔
228
                        return -ENOMEM;
229

230
                r = chmod_and_chown(a, 0644, uid, gid);
925✔
231
                if (r < 0) {
925✔
232
                        if (i->fatal)
×
233
                                return r;
234

235
                        log_debug_errno(r, "Failed to set access on cgroup %s, ignoring: %m", a);
925✔
236
                }
237
        }
238

239
        return 0;
240
}
241

242
struct access_callback_data {
243
        uid_t uid;
244
        gid_t gid;
245
        int error;
246
};
247

248
static int access_callback(
3,387✔
249
                RecurseDirEvent event,
250
                const char *path,
251
                int dir_fd,
252
                int inode_fd,
253
                const struct dirent *de,
254
                const struct statx *sx,
255
                void *userdata) {
256

257
        if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
3,387✔
258
                return RECURSE_DIR_CONTINUE;
259

260
        struct access_callback_data *d = ASSERT_PTR(userdata);
3,213✔
261

262
        assert(path);
3,213✔
263
        assert(inode_fd >= 0);
3,213✔
264

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

268
        return RECURSE_DIR_CONTINUE;
269
}
270

271
int cg_set_access_recursive(
321✔
272
                const char *path,
273
                uid_t uid,
274
                gid_t gid) {
275

276
        _cleanup_close_ int fd = -EBADF;
321✔
277
        _cleanup_free_ char *fs = NULL;
321✔
278
        int r;
321✔
279

280
        assert(path);
321✔
281

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

286
        if (!uid_is_valid(uid) && !gid_is_valid(gid))
468✔
287
                return 0;
288

289
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
174✔
290
        if (r < 0)
174✔
291
                return r;
292

293
        fd = open(fs, O_DIRECTORY|O_CLOEXEC);
174✔
294
        if (fd < 0)
174✔
295
                return -errno;
×
296

297
        struct access_callback_data d = {
174✔
298
                .uid = uid,
299
                .gid = gid,
300
        };
301

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

312
        assert(d.error <= 0);
174✔
313
        return d.error;
314
}
315

316
int cg_migrate(
244✔
317
                const char *from,
318
                const char *to,
319
                CGroupFlags flags) {
320

321
        _cleanup_set_free_ Set *s = NULL;
244✔
322
        bool done;
244✔
323
        int r, ret = 0;
244✔
324

325
        assert(from);
244✔
326
        assert(to);
244✔
327

328
        do {
244✔
329
                _cleanup_fclose_ FILE *f = NULL;
×
330
                pid_t pid;
244✔
331

332
                done = true;
244✔
333

334
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, from, &f);
244✔
335
                if (r < 0)
244✔
336
                        return RET_GATHER(ret, r);
×
337

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

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

348
                        if (set_contains(s, PID_TO_PTR(pid)))
1,830✔
349
                                continue;
×
350

351
                        if (pid_is_kernel_thread(pid) > 0)
1,830✔
352
                                continue;
1,830✔
353

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

361
                        done = false;
×
362

363
                        r = set_ensure_put(&s, /* hash_ops = */ NULL, PID_TO_PTR(pid));
×
364
                        if (r < 0)
×
365
                                return RET_GATHER(ret, r);
×
366
                }
367
                if (r < 0)
244✔
368
                        return RET_GATHER(ret, r);
×
369
        } while (!done);
244✔
370

371
        return ret;
372
}
373

374
int cg_enable(
4,053✔
375
                CGroupMask supported,
376
                CGroupMask mask,
377
                const char *p,
378
                CGroupMask *ret_result_mask) {
379

380
        _cleanup_fclose_ FILE *f = NULL;
4,053✔
381
        _cleanup_free_ char *fs = NULL;
4,053✔
382
        CGroupController c;
4,053✔
383
        CGroupMask ret = 0;
4,053✔
384
        int r;
4,053✔
385

386
        assert(p);
4,053✔
387

388
        if (supported == 0) {
4,053✔
389
                if (ret_result_mask)
×
390
                        *ret_result_mask = 0;
×
391
                return 0;
×
392
        }
393

394
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs);
4,053✔
395
        if (r < 0)
4,053✔
396
                return r;
397

398
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
56,742✔
399
                CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
52,689✔
400
                const char *n;
52,689✔
401

402
                if (!FLAGS_SET(CGROUP_MASK_V2, bit))
52,689✔
403
                        continue;
32,424✔
404

405
                if (!FLAGS_SET(supported, bit))
20,265✔
406
                        continue;
3,149✔
407

408
                n = cgroup_controller_to_string(c);
17,116✔
409
                {
17,116✔
410
                        char s[1 + strlen(n) + 1];
17,116✔
411

412
                        s[0] = FLAGS_SET(mask, bit) ? '+' : '-';
17,116✔
413
                        strcpy(s + 1, n);
17,116✔
414

415
                        if (!f) {
17,116✔
416
                                f = fopen(fs, "we");
4,030✔
417
                                if (!f)
4,030✔
418
                                        return log_debug_errno(errno, "Failed to open cgroup.subtree_control file of %s: %m", p);
×
419
                        }
420

421
                        r = write_string_stream(f, s, WRITE_STRING_FILE_DISABLE_BUFFER);
17,116✔
422
                        if (r < 0) {
17,116✔
423
                                log_debug_errno(r, "Failed to %s controller %s for %s (%s): %m",
6✔
424
                                                FLAGS_SET(mask, bit) ? "enable" : "disable", n, p, fs);
425
                                clearerr(f);
6✔
426

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

455
        /* Let's return the precise set of controllers now enabled for the cgroup. */
456
        if (ret_result_mask)
4,053✔
457
                *ret_result_mask = ret;
3,916✔
458

459
        return 0;
460
}
461

462
int cg_has_legacy(void) {
685✔
463
        struct statfs fs;
685✔
464

465
        /* Checks if any legacy controller/hierarchy is mounted. */
466

467
        if (statfs("/sys/fs/cgroup/", &fs) < 0) {
685✔
468
                if (errno == ENOENT) /* sysfs not mounted? */
×
469
                        return false;
685✔
470

471
                return log_error_errno(errno, "Failed to statfs /sys/fs/cgroup/: %m");
×
472
        }
473

474
        if (is_fs_type(&fs, CGROUP2_SUPER_MAGIC) ||
685✔
475
            is_fs_type(&fs, SYSFS_MAGIC)) /* not mounted yet */
×
476
                return false;
477

478
        if (is_fs_type(&fs, TMPFS_MAGIC)) {
×
479
                log_info("Found tmpfs on /sys/fs/cgroup/, assuming legacy hierarchy.");
×
480
                return true;
×
481
        }
482

483
        return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
484
                               "Unknown filesystem type %llx mounted on /sys/fs/cgroup/.",
485
                               (unsigned long long) fs.f_type);
486
}
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