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

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

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

83.3
/src/basic/cgroup-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <signal.h>
4
#include <stdlib.h>
5
#include <sys/xattr.h>
6
#include <threads.h>
7
#include <unistd.h>
8

9
#include "alloc-util.h"
10
#include "capsule-util.h"
11
#include "cgroup-util.h"
12
#include "dirent-util.h"
13
#include "errno-util.h"
14
#include "extract-word.h"
15
#include "fd-util.h"
16
#include "fileio.h"
17
#include "format-util.h"
18
#include "fs-util.h"
19
#include "log.h"
20
#include "login-util.h"
21
#include "missing_fs.h"
22
#include "missing_magic.h"
23
#include "parse-util.h"
24
#include "path-util.h"
25
#include "pidref.h"
26
#include "process-util.h"
27
#include "set.h"
28
#include "special.h"
29
#include "stat-util.h"
30
#include "string-table.h"
31
#include "string-util.h"
32
#include "strv.h"
33
#include "unit-name.h"
34
#include "user-util.h"
35
#include "xattr-util.h"
36

37
/* The structure to pass to name_to_handle_at() on cgroupfs2 */
38
typedef union {
39
        struct file_handle file_handle;
40
        uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
41
} cg_file_handle;
42

43
#define CG_FILE_HANDLE_INIT                                     \
44
        (cg_file_handle) {                                      \
45
                .file_handle.handle_bytes = sizeof(uint64_t),   \
46
                .file_handle.handle_type = FILEID_KERNFS,       \
47
        }
48

49
#define CG_FILE_HANDLE_CGROUPID(fh) (*CAST_ALIGN_PTR(uint64_t, (fh).file_handle.f_handle))
50

51
int cg_path_open(const char *controller, const char *path) {
790✔
52
        _cleanup_free_ char *fs = NULL;
790✔
53
        int r;
790✔
54

55
        r = cg_get_path(controller, path, /* suffix=*/ NULL, &fs);
790✔
56
        if (r < 0)
790✔
57
                return r;
58

59
        return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
790✔
60
}
61

62
int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) {
8✔
63
        _cleanup_close_ int fsfd = -EBADF;
8✔
64

65
        if (cgroupfs_fd < 0) {
8✔
66
                fsfd = open("/sys/fs/cgroup", O_CLOEXEC|O_DIRECTORY);
7✔
67
                if (fsfd < 0)
7✔
UNCOV
68
                        return -errno;
×
69

70
                cgroupfs_fd = fsfd;
71
        }
72

73
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
8✔
74
        CG_FILE_HANDLE_CGROUPID(fh) = id;
8✔
75

76
        return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
15✔
77
}
78

UNCOV
79
int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret) {
×
UNCOV
80
        _cleanup_close_ int cgfd = -EBADF;
×
81
        int r;
×
82

83
        cgfd = cg_cgroupid_open(cgroupfs_fd, id);
×
UNCOV
84
        if (cgfd < 0)
×
85
                return cgfd;
86

UNCOV
87
        _cleanup_free_ char *path = NULL;
×
UNCOV
88
        r = fd_get_path(cgfd, &path);
×
89
        if (r < 0)
×
90
                return r;
91

UNCOV
92
        if (!path_startswith(path, "/sys/fs/cgroup/"))
×
93
                return -EXDEV; /* recognizable error */
94

UNCOV
95
        if (ret)
×
UNCOV
96
                *ret = TAKE_PTR(path);
×
97
        return 0;
98
}
99

100
int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret) {
4,044✔
101
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
4,044✔
102
        int mnt_id;
4,044✔
103

104
        assert(dfd >= 0 || (dfd == AT_FDCWD && path_is_absolute(path)));
8,055✔
105
        assert(ret);
4,044✔
106

107
        /* This is cgroupfs so we know the size of the handle, thus no need to loop around like
108
         * name_to_handle_at_loop() does in mountpoint-util.c */
109
        if (name_to_handle_at(dfd, strempty(path), &fh.file_handle, &mnt_id, isempty(path) ? AT_EMPTY_PATH : 0) < 0) {
8,088✔
UNCOV
110
                assert(errno != EOVERFLOW);
×
UNCOV
111
                return -errno;
×
112
        }
113

114
        *ret = CG_FILE_HANDLE_CGROUPID(fh);
4,044✔
115
        return 0;
4,044✔
116
}
117

118
int cg_enumerate_processes(const char *controller, const char *path, FILE **ret) {
18,210✔
119
        _cleanup_free_ char *fs = NULL;
18,210✔
120
        FILE *f;
18,210✔
121
        int r;
18,210✔
122

123
        assert(ret);
18,210✔
124

125
        r = cg_get_path(controller, path, "cgroup.procs", &fs);
18,210✔
126
        if (r < 0)
18,210✔
127
                return r;
128

129
        f = fopen(fs, "re");
18,210✔
130
        if (!f)
18,210✔
131
                return -errno;
12,558✔
132

133
        *ret = f;
5,652✔
134
        return 0;
5,652✔
135
}
136

137
int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags) {
10,027✔
138
        unsigned long ul;
10,027✔
139

140
        /* Note that the cgroup.procs might contain duplicates! See cgroups.txt for details. */
141

142
        assert(f);
10,027✔
143
        assert(ret);
10,027✔
144

145
        for (;;) {
10,027✔
146
                errno = 0;
10,027✔
147
                if (fscanf(f, "%lu", &ul) != 1) {
10,027✔
148

149
                        if (feof(f)) {
5,882✔
150
                                *ret = 0;
5,882✔
151
                                return 0;
5,882✔
152
                        }
153

UNCOV
154
                        return errno_or_else(EIO);
×
155
                }
156

157
                if (ul > PID_T_MAX)
4,145✔
158
                        return -EIO;
159

160
                /* In some circumstances (e.g. WSL), cgroups might contain unmappable PIDs from other
161
                 * contexts. These show up as zeros, and depending on the caller, can either be plain
162
                 * skipped over, or returned as-is. */
163
                if (ul == 0 && !FLAGS_SET(flags, CGROUP_DONT_SKIP_UNMAPPED))
4,145✔
UNCOV
164
                        continue;
×
165

166
                *ret = (pid_t) ul;
4,145✔
167
                return 1;
4,145✔
168
        }
169
}
170

171
int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) {
7,278✔
172
        int r;
7,278✔
173

174
        assert(f);
7,278✔
175
        assert(ret);
7,278✔
176

UNCOV
177
        for (;;) {
×
178
                pid_t pid;
7,278✔
179

180
                r = cg_read_pid(f, &pid, flags);
7,278✔
181
                if (r < 0)
7,278✔
UNCOV
182
                        return log_debug_errno(r, "Failed to read pid from cgroup item: %m");
×
183
                if (r == 0) {
7,278✔
184
                        *ret = PIDREF_NULL;
5,278✔
185
                        return 0;
5,278✔
186
                }
187

188
                if (pid == 0)
2,000✔
189
                        return -EREMOTE;
190

191
                r = pidref_set_pid(ret, pid);
2,000✔
192
                if (r >= 0)
2,000✔
193
                        return 1;
UNCOV
194
                if (r != -ESRCH)
×
195
                        return r;
196

197
                /* ESRCH → gone by now? just skip over it, read the next */
198
        }
199
}
200

201
int cg_read_event(
6,628✔
202
                const char *controller,
203
                const char *path,
204
                const char *event,
205
                char **ret) {
206

207
        _cleanup_free_ char *events = NULL, *content = NULL;
6,628✔
208
        int r;
6,628✔
209

210
        r = cg_get_path(controller, path, "cgroup.events", &events);
6,628✔
211
        if (r < 0)
6,628✔
212
                return r;
213

214
        r = read_full_virtual_file(events, &content, NULL);
6,628✔
215
        if (r < 0)
6,628✔
216
                return r;
217

218
        for (const char *p = content;;) {
259✔
219
                _cleanup_free_ char *line = NULL, *key = NULL;
259✔
220
                const char *q;
259✔
221

222
                r = extract_first_word(&p, &line, "\n", 0);
259✔
223
                if (r < 0)
259✔
224
                        return r;
225
                if (r == 0)
259✔
226
                        return -ENOENT;
227

228
                q = line;
259✔
229
                r = extract_first_word(&q, &key, " ", 0);
259✔
230
                if (r < 0)
259✔
231
                        return r;
232
                if (r == 0)
259✔
233
                        return -EINVAL;
234

235
                if (!streq(key, event))
259✔
236
                        continue;
×
237

238
                return strdup_to(ret, q);
259✔
239
        }
240
}
241

242
bool cg_kill_supported(void) {
×
243
        static thread_local int supported = -1;
×
244

245
        if (supported >= 0)
×
246
                return supported;
×
247

248
        if (cg_all_unified() <= 0)
×
249
                return (supported = false);
×
250

UNCOV
251
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
×
UNCOV
252
                return (supported = true);
×
UNCOV
253
        if (errno != ENOENT)
×
UNCOV
254
                log_debug_errno(errno, "Failed to check whether cgroup.kill is available, assuming not: %m");
×
UNCOV
255
        return (supported = false);
×
256
}
257

258
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret) {
17,807✔
259
        _cleanup_free_ char *fs = NULL;
17,807✔
260
        DIR *d;
17,807✔
261
        int r;
17,807✔
262

263
        assert(ret);
17,807✔
264

265
        /* This is not recursive! */
266

267
        r = cg_get_path(controller, path, NULL, &fs);
17,807✔
268
        if (r < 0)
17,807✔
269
                return r;
270

271
        d = opendir(fs);
17,807✔
272
        if (!d)
17,807✔
273
                return -errno;
12,558✔
274

275
        *ret = d;
5,249✔
276
        return 0;
5,249✔
277
}
278

279
int cg_read_subgroup(DIR *d, char **ret) {
6,603✔
280
        assert(d);
6,603✔
281
        assert(ret);
6,603✔
282

283
        FOREACH_DIRENT_ALL(de, d, return -errno) {
251,500✔
284
                if (de->d_type != DT_DIR)
246,021✔
285
                        continue;
233,939✔
286

287
                if (dot_or_dot_dot(de->d_name))
12,082✔
288
                        continue;
10,958✔
289

290
                return strdup_to_full(ret, de->d_name);
1,124✔
291
        }
292

293
        *ret = NULL;
5,479✔
294
        return 0;
5,479✔
295
}
296

297
int cg_kill(
17,707✔
298
                const char *path,
299
                int sig,
300
                CGroupFlags flags,
301
                Set *killed_pids,
302
                cg_kill_log_func_t log_kill,
303
                void *userdata) {
304

305
        _cleanup_set_free_ Set *allocated_set = NULL;
17,707✔
306
        int r, ret = 0;
17,707✔
307

308
        assert(path);
17,707✔
309
        assert(sig >= 0);
17,707✔
310

311
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
312
          * don't send SIGCONT on SIGKILL. */
313
        if (IN_SET(sig, SIGCONT, SIGKILL))
17,707✔
314
                flags &= ~CGROUP_SIGCONT;
4,200✔
315

316
        /* This goes through the tasks list and kills them all. This is repeated until no further processes
317
         * are added to the tasks list, to properly handle forking processes.
318
         *
319
         * When sending SIGKILL, prefer cg_kill_kernel_sigkill(), which is fully atomic. */
320

321
        if (!killed_pids) {
17,707✔
322
                killed_pids = allocated_set = set_new(NULL);
651✔
323
                if (!killed_pids)
651✔
324
                        return -ENOMEM;
325
        }
326

327
        bool done;
17,796✔
328
        do {
17,796✔
329
                _cleanup_fclose_ FILE *f = NULL;
12,558✔
330
                int ret_log_kill;
17,796✔
331

332
                done = true;
17,796✔
333

334
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f);
17,796✔
335
                if (r == -ENOENT)
17,796✔
336
                        break;
337
                if (r < 0)
5,238✔
UNCOV
338
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
339

340
                for (;;) {
7,137✔
341
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
7,137✔
342

343
                        r = cg_read_pidref(f, &pidref, flags);
7,137✔
344
                        if (r < 0)
7,137✔
UNCOV
345
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
346
                        if (r == 0)
7,137✔
347
                                break;
348

349
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
1,899✔
350
                                continue;
651✔
351

352
                        if (set_contains(killed_pids, PID_TO_PTR(pidref.pid)))
1,248✔
353
                                continue;
874✔
354

355
                        /* Ignore kernel threads to mimic the behavior of cgroup.kill. */
356
                        if (pidref_is_kernel_thread(&pidref) > 0) {
374✔
UNCOV
357
                                log_debug("Ignoring kernel thread with pid " PID_FMT " in cgroup '%s'", pidref.pid, path);
×
UNCOV
358
                                continue;
×
359
                        }
360

361
                        if (log_kill)
374✔
362
                                ret_log_kill = log_kill(&pidref, sig, userdata);
97✔
363

364
                        /* If we haven't killed this process yet, kill it */
365
                        r = pidref_kill(&pidref, sig);
374✔
366
                        if (r < 0 && r != -ESRCH)
374✔
UNCOV
367
                                RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path));
×
368
                        if (r >= 0) {
374✔
369
                                if (flags & CGROUP_SIGCONT)
374✔
370
                                        (void) pidref_kill(&pidref, SIGCONT);
276✔
371

372
                                if (ret == 0) {
374✔
373
                                        if (log_kill)
165✔
374
                                                ret = ret_log_kill;
375
                                        else
376
                                                ret = 1;
68✔
377
                                }
378
                        }
379

380
                        done = false;
374✔
381

382
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
374✔
383
                        if (r < 0)
374✔
UNCOV
384
                                return RET_GATHER(ret, r);
×
385
                }
386

387
                /* To avoid racing against processes which fork quicker than we can kill them, we repeat this
388
                 * until no new pids need to be killed. */
389

390
        } while (!done);
5,238✔
391

392
        return ret;
393
}
394

395
int cg_kill_recursive(
17,054✔
396
                const char *path,
397
                int sig,
398
                CGroupFlags flags,
399
                Set *killed_pids,
400
                cg_kill_log_func_t log_kill,
401
                void *userdata) {
402

UNCOV
403
        _cleanup_set_free_ Set *allocated_set = NULL;
×
404
        _cleanup_closedir_ DIR *d = NULL;
17,054✔
405
        int r, ret;
17,054✔
406

407
        assert(path);
17,054✔
408
        assert(sig >= 0);
17,054✔
409

410
        if (!killed_pids) {
17,054✔
411
                killed_pids = allocated_set = set_new(NULL);
16,437✔
412
                if (!killed_pids)
16,437✔
413
                        return -ENOMEM;
414
        }
415

416
        ret = cg_kill(path, sig, flags, killed_pids, log_kill, userdata);
17,054✔
417

418
        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d);
17,054✔
419
        if (r < 0) {
17,054✔
420
                if (r != -ENOENT)
12,558✔
UNCOV
421
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
422

423
                return ret;
12,558✔
424
        }
425

426
        for (;;) {
4,628✔
427
                _cleanup_free_ char *fn = NULL, *p = NULL;
4,562✔
428

429
                r = cg_read_subgroup(d, &fn);
4,562✔
430
                if (r < 0) {
4,562✔
UNCOV
431
                        RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path));
×
432
                        break;
433
                }
434
                if (r == 0)
4,562✔
435
                        break;
436

437
                p = path_join(empty_to_root(path), fn);
66✔
438
                if (!p)
66✔
UNCOV
439
                        return -ENOMEM;
×
440

441
                r = cg_kill_recursive(p, sig, flags, killed_pids, log_kill, userdata);
66✔
442
                if (r < 0)
66✔
UNCOV
443
                        log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p);
×
444
                if (r != 0 && ret >= 0)
66✔
445
                        ret = r;
15✔
446
        }
447

448
        return ret;
4,496✔
449
}
450

UNCOV
451
int cg_kill_kernel_sigkill(const char *path) {
×
452
        _cleanup_free_ char *killfile = NULL;
×
UNCOV
453
        int r;
×
454

455
        /* Kills the cgroup at `path` directly by writing to its cgroup.kill file.  This sends SIGKILL to all
456
         * processes in the cgroup and has the advantage of being completely atomic, unlike cg_kill_items(). */
457

458
        assert(path);
×
459

UNCOV
460
        if (!cg_kill_supported())
×
461
                return -EOPNOTSUPP;
462

463
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.kill", &killfile);
×
UNCOV
464
        if (r < 0)
×
465
                return r;
466

UNCOV
467
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
×
468
        if (r < 0)
×
469
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
470

471
        return 0;
472
}
473

UNCOV
474
static const char *controller_to_dirname(const char *controller) {
×
475
        assert(controller);
×
476

477
        /* Converts a controller name to the directory name below /sys/fs/cgroup/ we want to mount it
478
         * to. Effectively, this just cuts off the name= prefixed used for named hierarchies, if it is
479
         * specified. */
480

UNCOV
481
        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
×
482
                if (cg_hybrid_unified() > 0)
×
483
                        controller = SYSTEMD_CGROUP_CONTROLLER_HYBRID;
484
                else
485
                        controller = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
×
486
        }
487

UNCOV
488
        return startswith(controller, "name=") ?: controller;
×
489
}
490

UNCOV
491
static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **ret) {
×
492
        const char *dn;
×
UNCOV
493
        char *t = NULL;
×
494

495
        assert(ret);
×
496
        assert(controller);
×
497

498
        dn = controller_to_dirname(controller);
×
499

UNCOV
500
        if (isempty(path) && isempty(suffix))
×
501
                t = path_join("/sys/fs/cgroup", dn);
×
502
        else if (isempty(path))
×
UNCOV
503
                t = path_join("/sys/fs/cgroup", dn, suffix);
×
UNCOV
504
        else if (isempty(suffix))
×
505
                t = path_join("/sys/fs/cgroup", dn, path);
×
506
        else
UNCOV
507
                t = path_join("/sys/fs/cgroup", dn, path, suffix);
×
UNCOV
508
        if (!t)
×
509
                return -ENOMEM;
510

UNCOV
511
        *ret = t;
×
UNCOV
512
        return 0;
×
513
}
514

515
static int join_path_unified(const char *path, const char *suffix, char **ret) {
236,257✔
516
        char *t;
236,257✔
517

518
        assert(ret);
236,257✔
519

520
        if (isempty(path) && isempty(suffix))
250,369✔
521
                t = strdup("/sys/fs/cgroup");
1,105✔
522
        else if (isempty(path))
235,152✔
523
                t = path_join("/sys/fs/cgroup", suffix);
13,007✔
524
        else if (isempty(suffix))
222,145✔
525
                t = path_join("/sys/fs/cgroup", path);
81,315✔
526
        else
527
                t = path_join("/sys/fs/cgroup", path, suffix);
140,830✔
528
        if (!t)
236,257✔
529
                return -ENOMEM;
530

531
        *ret = t;
236,257✔
532
        return 0;
236,257✔
533
}
534

535
int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret) {
236,514✔
536
        int r;
236,514✔
537

538
        assert(ret);
236,514✔
539

540
        if (!controller) {
236,514✔
541
                char *t;
257✔
542

543
                /* If no controller is specified, we return the path *below* the controllers, without any
544
                 * prefix. */
545

546
                if (isempty(path) && isempty(suffix))
257✔
547
                        return -EINVAL;
548

549
                if (isempty(suffix))
257✔
UNCOV
550
                        t = strdup(path);
×
551
                else if (isempty(path))
257✔
UNCOV
552
                        t = strdup(suffix);
×
553
                else
554
                        t = path_join(path, suffix);
257✔
555
                if (!t)
257✔
556
                        return -ENOMEM;
557

558
                *ret = path_simplify(t);
257✔
559
                return 0;
257✔
560
        }
561

562
        if (!cg_controller_is_valid(controller))
236,257✔
563
                return -EINVAL;
564

565
        r = cg_all_unified();
236,257✔
566
        if (r < 0)
236,257✔
567
                return r;
568
        if (r > 0)
236,257✔
569
                r = join_path_unified(path, suffix, ret);
236,257✔
570
        else
UNCOV
571
                r = join_path_legacy(controller, path, suffix, ret);
×
572
        if (r < 0)
236,257✔
573
                return r;
574

575
        path_simplify(*ret);
236,257✔
576
        return 0;
236,257✔
577
}
578

UNCOV
579
static int controller_is_v1_accessible(const char *root, const char *controller) {
×
UNCOV
580
        const char *cpath, *dn;
×
581

UNCOV
582
        assert(controller);
×
583

584
        dn = controller_to_dirname(controller);
×
585

586
        /* If root if specified, we check that:
587
         * - possible subcgroup is created at root,
588
         * - we can modify the hierarchy. */
589

UNCOV
590
        cpath = strjoina("/sys/fs/cgroup/", dn, root, root ? "/cgroup.procs" : NULL);
×
UNCOV
591
        return access_nofollow(cpath, root ? W_OK : F_OK);
×
592
}
593

594
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret) {
18,725✔
595
        int r;
18,725✔
596

597
        assert(controller);
18,725✔
598
        assert(ret);
18,725✔
599

600
        if (!cg_controller_is_valid(controller))
18,725✔
601
                return -EINVAL;
602

603
        r = cg_all_unified();
18,725✔
604
        if (r < 0)
18,725✔
605
                return r;
606
        if (r > 0) {
18,725✔
607
                /* In the unified hierarchy all controllers are considered accessible,
608
                 * except for the named hierarchies */
609
                if (startswith(controller, "name="))
18,725✔
610
                        return -EOPNOTSUPP;
611
        } else {
612
                /* Check if the specified controller is actually accessible */
UNCOV
613
                r = controller_is_v1_accessible(NULL, controller);
×
UNCOV
614
                if (r < 0)
×
615
                        return r;
616
        }
617

618
        return cg_get_path(controller, path, suffix, ret);
18,725✔
619
}
620

621
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
4,456✔
622
        _cleanup_free_ char *fs = NULL;
4,456✔
623
        int r;
4,456✔
624

625
        assert(path);
4,456✔
626
        assert(name);
4,456✔
627
        assert(value || size <= 0);
4,456✔
628

629
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
4,456✔
630
        if (r < 0)
4,456✔
631
                return r;
632

633
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
4,456✔
634
}
635

636
int cg_get_xattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size) {
15,767✔
637
        _cleanup_free_ char *fs = NULL;
15,767✔
638
        int r;
15,767✔
639

640
        assert(path);
15,767✔
641
        assert(name);
15,767✔
642

643
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
15,767✔
644
        if (r < 0)
15,767✔
645
                return r;
646

647
        return lgetxattr_malloc(fs, name, ret, ret_size);
15,767✔
648
}
649

650
int cg_get_xattr_bool(const char *path, const char *name) {
147✔
651
        _cleanup_free_ char *fs = NULL;
147✔
652
        int r;
147✔
653

654
        assert(path);
147✔
655
        assert(name);
147✔
656

657
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
147✔
658
        if (r < 0)
147✔
659
                return r;
660

661
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
147✔
662
}
663

664
int cg_remove_xattr(const char *path, const char *name) {
24,751✔
665
        _cleanup_free_ char *fs = NULL;
24,751✔
666
        int r;
24,751✔
667

668
        assert(path);
24,751✔
669
        assert(name);
24,751✔
670

671
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
24,751✔
672
        if (r < 0)
24,751✔
673
                return r;
674

675
        return RET_NERRNO(removexattr(fs, name));
49,502✔
676
}
677

678
int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
41,387✔
679
        _cleanup_fclose_ FILE *f = NULL;
41,387✔
680
        const char *fs, *controller_str = NULL;  /* avoid false maybe-uninitialized warning */
41,387✔
681
        int unified, r;
41,387✔
682

683
        assert(pid >= 0);
41,387✔
684
        assert(ret_path);
41,387✔
685

686
        if (controller) {
41,387✔
687
                if (!cg_controller_is_valid(controller))
41,204✔
688
                        return -EINVAL;
689
        } else
690
                controller = SYSTEMD_CGROUP_CONTROLLER;
691

692
        unified = cg_unified_controller(controller);
41,387✔
693
        if (unified < 0)
41,387✔
694
                return unified;
695
        if (unified == 0) {
41,387✔
UNCOV
696
                if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
×
697
                        controller_str = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
698
                else
UNCOV
699
                        controller_str = controller;
×
700
        }
701

702
        fs = procfs_file_alloca(pid, "cgroup");
48,239✔
703
        r = fopen_unlocked(fs, "re", &f);
41,387✔
704
        if (r == -ENOENT)
41,387✔
705
                return -ESRCH;
706
        if (r < 0)
37,229✔
707
                return r;
708

709
        for (;;) {
37,229✔
710
                _cleanup_free_ char *line = NULL;
37,229✔
711
                char *e;
37,229✔
712

713
                r = read_line(f, LONG_LINE_MAX, &line);
37,229✔
714
                if (r < 0)
37,229✔
715
                        return r;
716
                if (r == 0)
37,225✔
717
                        return -ENODATA;
718

719
                if (unified) {
37,225✔
720
                        e = startswith(line, "0:");
37,225✔
721
                        if (!e)
37,225✔
722
                                continue;
×
723

724
                        e = strchr(e, ':');
37,225✔
725
                        if (!e)
37,225✔
726
                                continue;
×
727
                } else {
728
                        char *l;
×
729

730
                        l = strchr(line, ':');
×
731
                        if (!l)
×
732
                                continue;
×
733

734
                        l++;
×
735
                        e = strchr(l, ':');
×
736
                        if (!e)
×
UNCOV
737
                                continue;
×
738
                        *e = 0;
×
739

UNCOV
740
                        assert(controller_str);
×
UNCOV
741
                        r = string_contains_word(l, ",", controller_str);
×
UNCOV
742
                        if (r < 0)
×
743
                                return r;
UNCOV
744
                        if (r == 0)
×
UNCOV
745
                                continue;
×
746
                }
747

748
                _cleanup_free_ char *path = strdup(e + 1);
37,225✔
749
                if (!path)
37,225✔
750
                        return -ENOMEM;
751

752
                /* Refuse cgroup paths from outside our cgroup namespace */
753
                if (startswith(path, "/../"))
37,225✔
754
                        return -EUNATCH;
755

756
                /* Truncate suffix indicating the process is a zombie */
757
                e = endswith(path, " (deleted)");
37,225✔
758
                if (e)
37,225✔
759
                        *e = 0;
288✔
760

761
                *ret_path = TAKE_PTR(path);
37,225✔
762
                return 0;
37,225✔
763
        }
764
}
765

766
int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_path) {
10,829✔
767
        _cleanup_free_ char *path = NULL;
10,829✔
768
        int r;
10,829✔
769

770
        assert(ret_path);
10,829✔
771

772
        if (!pidref_is_set(pidref))
10,829✔
773
                return -ESRCH;
774
        if (pidref_is_remote(pidref))
21,658✔
775
                return -EREMOTE;
776

777
        // XXX: Ideally we'd use pidfd_get_cgroupid() + cg_path_from_cgroupid() here, to extract this
778
        // bit of information from pidfd directly. However, the latter requires privilege and it's
779
        // not entirely clear how to handle cgroups from outer namespace.
780

781
        r = cg_pid_get_path(controller, pidref->pid, &path);
10,829✔
782
        if (r < 0)
10,829✔
783
                return r;
784

785
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
786
        r = pidref_verify(pidref);
10,827✔
787
        if (r < 0)
10,827✔
788
                return r;
789

790
        *ret_path = TAKE_PTR(path);
10,827✔
791
        return 0;
10,827✔
792
}
793

794
int cg_is_empty(const char *controller, const char *path) {
4✔
795
        _cleanup_fclose_ FILE *f = NULL;
4✔
796
        pid_t pid;
4✔
797
        int r;
4✔
798

799
        assert(path);
4✔
800

801
        r = cg_enumerate_processes(controller, path, &f);
4✔
802
        if (r == -ENOENT)
4✔
803
                return true;
804
        if (r < 0)
4✔
805
                return r;
806

807
        r = cg_read_pid(f, &pid, CGROUP_DONT_SKIP_UNMAPPED);
4✔
808
        if (r < 0)
4✔
809
                return r;
810

811
        return r == 0;
4✔
812
}
813

814
int cg_is_empty_recursive(const char *controller, const char *path) {
6,628✔
815
        int r;
6,628✔
816

817
        assert(path);
6,628✔
818

819
        /* The root cgroup is always populated */
820
        if (controller && empty_or_root(path))
6,628✔
821
                return false;
822

823
        r = cg_unified_controller(controller);
6,628✔
824
        if (r < 0)
6,628✔
825
                return r;
826
        if (r > 0) {
6,628✔
827
                _cleanup_free_ char *t = NULL;
6,628✔
828

829
                /* On the unified hierarchy we can check empty state
830
                 * via the "populated" attribute of "cgroup.events". */
831

832
                r = cg_read_event(controller, path, "populated", &t);
6,628✔
833
                if (r == -ENOENT)
6,628✔
834
                        return true;
835
                if (r < 0)
259✔
836
                        return r;
837

838
                return streq(t, "0");
259✔
839
        } else {
UNCOV
840
                _cleanup_closedir_ DIR *d = NULL;
×
841
                char *fn;
×
842

UNCOV
843
                r = cg_is_empty(controller, path);
×
844
                if (r <= 0)
×
845
                        return r;
846

847
                r = cg_enumerate_subgroups(controller, path, &d);
×
848
                if (r == -ENOENT)
×
849
                        return true;
850
                if (r < 0)
×
851
                        return r;
852

UNCOV
853
                while ((r = cg_read_subgroup(d, &fn)) > 0) {
×
UNCOV
854
                        _cleanup_free_ char *p = NULL;
×
855

856
                        p = path_join(path, fn);
×
UNCOV
857
                        free(fn);
×
UNCOV
858
                        if (!p)
×
859
                                return -ENOMEM;
860

UNCOV
861
                        r = cg_is_empty_recursive(controller, p);
×
862
                        if (r <= 0)
×
863
                                return r;
864
                }
UNCOV
865
                if (r < 0)
×
866
                        return r;
867

UNCOV
868
                return true;
×
869
        }
870
}
871

872
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
23✔
873
        _cleanup_free_ char *controller = NULL, *path = NULL;
23✔
874
        int r;
23✔
875

876
        assert(spec);
23✔
877

878
        if (*spec == '/') {
23✔
879
                if (!path_is_normalized(spec))
15✔
880
                        return -EINVAL;
881

882
                if (ret_path) {
15✔
883
                        r = path_simplify_alloc(spec, &path);
15✔
884
                        if (r < 0)
15✔
885
                                return r;
886
                }
887

888
        } else {
889
                const char *e;
8✔
890

891
                e = strchr(spec, ':');
8✔
892
                if (e) {
8✔
893
                        controller = strndup(spec, e-spec);
6✔
894
                        if (!controller)
6✔
895
                                return -ENOMEM;
896
                        if (!cg_controller_is_valid(controller))
6✔
897
                                return -EINVAL;
898

899
                        if (!isempty(e + 1)) {
3✔
900
                                path = strdup(e+1);
2✔
901
                                if (!path)
2✔
902
                                        return -ENOMEM;
903

904
                                if (!path_is_normalized(path) ||
2✔
905
                                    !path_is_absolute(path))
2✔
906
                                        return -EINVAL;
907

908
                                path_simplify(path);
1✔
909
                        }
910

911
                } else {
912
                        if (!cg_controller_is_valid(spec))
2✔
913
                                return -EINVAL;
914

915
                        if (ret_controller) {
1✔
916
                                controller = strdup(spec);
1✔
917
                                if (!controller)
1✔
918
                                        return -ENOMEM;
919
                        }
920
                }
921
        }
922

923
        if (ret_controller)
18✔
924
                *ret_controller = TAKE_PTR(controller);
18✔
925
        if (ret_path)
18✔
926
                *ret_path = TAKE_PTR(path);
18✔
927
        return 0;
928
}
929

930
int cg_mangle_path(const char *path, char **ret) {
465✔
931
        _cleanup_free_ char *c = NULL, *p = NULL;
465✔
932
        int r;
465✔
933

934
        assert(path);
465✔
935
        assert(ret);
465✔
936

937
        /* First, check if it already is a filesystem path */
938
        if (path_startswith(path, "/sys/fs/cgroup"))
465✔
939
                return path_simplify_alloc(path, ret);
461✔
940

941
        /* Otherwise, treat it as cg spec */
942
        r = cg_split_spec(path, &c, &p);
4✔
943
        if (r < 0)
4✔
944
                return r;
945

946
        return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, ret);
8✔
947
}
948

949
int cg_get_root_path(char **ret_path) {
13,724✔
950
        char *p, *e;
13,724✔
951
        int r;
13,724✔
952

953
        assert(ret_path);
13,724✔
954

955
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
13,724✔
956
        if (r < 0)
13,724✔
957
                return r;
13,724✔
958

959
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
13,724✔
960
        if (e)
13,724✔
961
                *e = 0;
13,694✔
962

963
        *ret_path = p;
13,724✔
964
        return 0;
13,724✔
965
}
966

967
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
11,233✔
968
        int r;
11,233✔
969

970
        assert(cgroup);
11,233✔
971
        assert(ret_shifted);
11,233✔
972

973
        _cleanup_free_ char *rt = NULL;
11,233✔
974
        if (!root) {
11,233✔
975
                /* If the root was specified let's use that, otherwise
976
                 * let's determine it from PID 1 */
977

978
                r = cg_get_root_path(&rt);
1,990✔
979
                if (r < 0)
1,990✔
980
                        return r;
981

982
                root = rt;
1,990✔
983
        }
984

985
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
11,233✔
986
        return 0;
11,233✔
987
}
988

989
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
15,217✔
990
        _cleanup_free_ char *raw = NULL;
15,217✔
991
        const char *c;
15,217✔
992
        int r;
15,217✔
993

994
        assert(pid >= 0);
15,217✔
995
        assert(ret_cgroup);
15,217✔
996

997
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw);
15,217✔
998
        if (r < 0)
15,217✔
999
                return r;
1000

1001
        r = cg_shift_path(raw, root, &c);
11,057✔
1002
        if (r < 0)
11,057✔
1003
                return r;
1004

1005
        if (c == raw) {
11,057✔
1006
                *ret_cgroup = TAKE_PTR(raw);
11,057✔
1007
                return 0;
11,057✔
1008
        }
1009

UNCOV
1010
        return strdup_to(ret_cgroup, c);
×
1011
}
1012

1013
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
32,343✔
1014
        assert(cgroup);
32,343✔
1015
        assert(ret_unit);
32,343✔
1016

1017
        size_t n = strcspn(cgroup, "/");
32,343✔
1018
        if (n < 3)
32,343✔
1019
                return -ENXIO;
1020

1021
        char *c = strndupa_safe(cgroup, n);
32,335✔
1022
        c = cg_unescape(c);
32,335✔
1023

1024
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
32,335✔
1025
                return -ENXIO;
1026

1027
        return strdup_to(ret_unit, c);
32,327✔
1028
}
1029

1030
static bool valid_slice_name(const char *p, size_t n) {
115,143✔
1031

1032
        if (!p)
115,143✔
1033
                return false;
1034

1035
        if (n < STRLEN("x.slice"))
115,127✔
1036
                return false;
1037

1038
        if (memcmp(p + n - 6, ".slice", 6) == 0) {
115,108✔
1039
                char buf[n+1], *c;
58,389✔
1040

1041
                memcpy(buf, p, n);
58,389✔
1042
                buf[n] = 0;
58,389✔
1043

1044
                c = cg_unescape(buf);
58,389✔
1045

1046
                return unit_name_is_valid(c, UNIT_NAME_PLAIN);
58,389✔
1047
        }
1048

1049
        return false;
1050
}
1051

1052
static const char *skip_slices(const char *p) {
40,988✔
1053
        assert(p);
40,988✔
1054

1055
        /* Skips over all slice assignments */
1056

1057
        for (;;) {
125,250✔
1058
                size_t n;
83,119✔
1059

1060
                p += strspn(p, "/");
83,119✔
1061

1062
                n = strcspn(p, "/");
83,119✔
1063
                if (!valid_slice_name(p, n))
83,119✔
1064
                        return p;
40,988✔
1065

1066
                p += n;
42,131✔
1067
        }
1068
}
1069

1070
int cg_path_get_unit(const char *path, char **ret) {
16,797✔
1071
        _cleanup_free_ char *unit = NULL;
16,797✔
1072
        const char *e;
16,797✔
1073
        int r;
16,797✔
1074

1075
        assert(path);
16,797✔
1076
        assert(ret);
16,797✔
1077

1078
        e = skip_slices(path);
16,797✔
1079

1080
        r = cg_path_decode_unit(e, &unit);
16,797✔
1081
        if (r < 0)
16,797✔
1082
                return r;
1083

1084
        /* We skipped over the slices, don't accept any now */
1085
        if (endswith(unit, ".slice"))
16,785✔
1086
                return -ENXIO;
1087

1088
        *ret = TAKE_PTR(unit);
16,785✔
1089
        return 0;
16,785✔
1090
}
1091

1092
int cg_path_get_unit_path(const char *path, char **ret) {
9,083✔
1093
        _cleanup_free_ char *path_copy = NULL;
9,083✔
1094
        char *unit_name;
9,083✔
1095

1096
        assert(path);
9,083✔
1097
        assert(ret);
9,083✔
1098

1099
        path_copy = strdup(path);
9,083✔
1100
        if (!path_copy)
9,083✔
1101
                return -ENOMEM;
1102

1103
        unit_name = (char *)skip_slices(path_copy);
9,083✔
1104
        unit_name[strcspn(unit_name, "/")] = 0;
9,083✔
1105

1106
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,083✔
1107
                return -ENXIO;
1108

1109
        *ret = TAKE_PTR(path_copy);
9,080✔
1110

1111
        return 0;
9,080✔
1112
}
1113

1114
int cg_pid_get_unit(pid_t pid, char **ret_unit) {
622✔
1115
        _cleanup_free_ char *cgroup = NULL;
622✔
1116
        int r;
622✔
1117

1118
        assert(ret_unit);
622✔
1119

1120
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
622✔
1121
        if (r < 0)
622✔
1122
                return r;
1123

1124
        return cg_path_get_unit(cgroup, ret_unit);
618✔
1125
}
1126

1127
int cg_pidref_get_unit(const PidRef *pidref, char **ret) {
546✔
1128
        _cleanup_free_ char *unit = NULL;
546✔
1129
        int r;
546✔
1130

1131
        assert(ret);
546✔
1132

1133
        if (!pidref_is_set(pidref))
546✔
1134
                return -ESRCH;
1135
        if (pidref_is_remote(pidref))
1,092✔
1136
                return -EREMOTE;
1137

1138
        r = cg_pid_get_unit(pidref->pid, &unit);
546✔
1139
        if (r < 0)
546✔
1140
                return r;
1141

1142
        r = pidref_verify(pidref);
542✔
1143
        if (r < 0)
542✔
1144
                return r;
1145

1146
        *ret = TAKE_PTR(unit);
542✔
1147
        return 0;
542✔
1148
}
1149

1150
/**
1151
 * Skip session-*.scope, but require it to be there.
1152
 */
1153
static const char *skip_session(const char *p) {
14,701✔
1154
        size_t n;
14,701✔
1155

1156
        if (isempty(p))
14,701✔
1157
                return NULL;
1158

1159
        p += strspn(p, "/");
14,697✔
1160

1161
        n = strcspn(p, "/");
14,697✔
1162
        if (n < STRLEN("session-x.scope"))
14,697✔
1163
                return NULL;
1164

1165
        if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) {
14,535✔
1166
                char buf[n - 8 - 6 + 1];
23✔
1167

1168
                memcpy(buf, p + 8, n - 8 - 6);
23✔
1169
                buf[n - 8 - 6] = 0;
23✔
1170

1171
                /* Note that session scopes never need unescaping,
1172
                 * since they cannot conflict with the kernel's own
1173
                 * names, hence we don't need to call cg_unescape()
1174
                 * here. */
1175

1176
                if (!session_id_valid(buf))
23✔
1177
                        return NULL;
23✔
1178

1179
                p += n;
23✔
1180
                p += strspn(p, "/");
23✔
1181
                return p;
23✔
1182
        }
1183

1184
        return NULL;
1185
}
1186

1187
/**
1188
 * Skip user@*.service or capsule@*.service, but require either of them to be there.
1189
 */
1190
static const char *skip_user_manager(const char *p) {
15,108✔
1191
        size_t n;
15,108✔
1192

1193
        if (isempty(p))
15,108✔
1194
                return NULL;
15,108✔
1195

1196
        p += strspn(p, "/");
15,104✔
1197

1198
        n = strcspn(p, "/");
15,104✔
1199
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,104✔
1200
                return NULL;
1201

1202
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
1203
         * resolve user/capsule name. */
1204
        _cleanup_free_ char *unit_name = strndup(p, n);
14,942✔
1205
        if (!unit_name)
14,942✔
1206
                return NULL;
1207

1208
        _cleanup_free_ char *i = NULL;
14,942✔
1209
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
14,942✔
1210

1211
        if (type != UNIT_NAME_INSTANCE)
14,942✔
1212
                return NULL;
1213

1214
        /* Note that user manager services never need unescaping, since they cannot conflict with the
1215
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
1216
         * instance names, they should be always valid as we validate them upon unit start. */
1217
        if (startswith(unit_name, "user@")) {
485✔
1218
                if (parse_uid(i, NULL) < 0)
402✔
1219
                        return NULL;
1220

1221
                p += n;
402✔
1222
                p += strspn(p, "/");
402✔
1223
                return p;
402✔
1224
        } else if (startswith(unit_name, "capsule@")) {
83✔
1225
                if (capsule_name_is_valid(i) <= 0)
5✔
1226
                        return NULL;
1227

1228
                p += n;
5✔
1229
                p += strspn(p, "/");
5✔
1230
                return p;
5✔
1231
        }
1232

1233
        return NULL;
1234
}
1235

1236
static const char *skip_user_prefix(const char *path) {
15,108✔
1237
        const char *e, *t;
15,108✔
1238

1239
        assert(path);
15,108✔
1240

1241
        /* Skip slices, if there are any */
1242
        e = skip_slices(path);
15,108✔
1243

1244
        /* Skip the user manager, if it's in the path now... */
1245
        t = skip_user_manager(e);
15,108✔
1246
        if (t)
15,108✔
1247
                return t;
1248

1249
        /* Alternatively skip the user session if it is in the path... */
1250
        return skip_session(e);
14,701✔
1251
}
1252

1253
int cg_path_get_user_unit(const char *path, char **ret) {
7,581✔
1254
        const char *t;
7,581✔
1255

1256
        assert(path);
7,581✔
1257
        assert(ret);
7,581✔
1258

1259
        t = skip_user_prefix(path);
7,581✔
1260
        if (!t)
7,581✔
1261
                return -ENXIO;
1262

1263
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
1264
         * parser. */
1265
        return cg_path_get_unit(t, ret);
221✔
1266
}
1267

1268
int cg_pid_get_user_unit(pid_t pid, char **ret_unit) {
55✔
1269
        _cleanup_free_ char *cgroup = NULL;
55✔
1270
        int r;
55✔
1271

1272
        assert(ret_unit);
55✔
1273

1274
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
55✔
1275
        if (r < 0)
55✔
1276
                return r;
1277

1278
        return cg_path_get_user_unit(cgroup, ret_unit);
55✔
1279
}
1280

1281
int cg_path_get_machine_name(const char *path, char **ret_machine) {
39✔
1282
        _cleanup_free_ char *u = NULL;
39✔
1283
        const char *sl;
39✔
1284
        int r;
39✔
1285

1286
        r = cg_path_get_unit(path, &u);
39✔
1287
        if (r < 0)
39✔
1288
                return r;
1289

1290
        sl = strjoina("/run/systemd/machines/unit:", u);
195✔
1291
        return readlink_malloc(sl, ret_machine);
39✔
1292
}
1293

1294
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
39✔
1295
        _cleanup_free_ char *cgroup = NULL;
39✔
1296
        int r;
39✔
1297

1298
        assert(ret_machine);
39✔
1299

1300
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
39✔
1301
        if (r < 0)
39✔
1302
                return r;
1303

1304
        return cg_path_get_machine_name(cgroup, ret_machine);
39✔
1305
}
1306

1307
int cg_path_get_session(const char *path, char **ret_session) {
8,341✔
1308
        _cleanup_free_ char *unit = NULL;
8,341✔
1309
        char *start, *end;
8,341✔
1310
        int r;
8,341✔
1311

1312
        assert(path);
8,341✔
1313

1314
        r = cg_path_get_unit(path, &unit);
8,341✔
1315
        if (r < 0)
8,341✔
1316
                return r;
1317

1318
        start = startswith(unit, "session-");
8,340✔
1319
        if (!start)
8,340✔
1320
                return -ENXIO;
1321
        end = endswith(start, ".scope");
326✔
1322
        if (!end)
326✔
1323
                return -ENXIO;
1324

1325
        *end = 0;
326✔
1326
        if (!session_id_valid(start))
326✔
1327
                return -ENXIO;
1328

1329
        if (!ret_session)
325✔
1330
                return 0;
1331

1332
        return strdup_to(ret_session, start);
325✔
1333
}
1334

1335
int cg_pid_get_session(pid_t pid, char **ret_session) {
755✔
1336
        _cleanup_free_ char *cgroup = NULL;
755✔
1337
        int r;
755✔
1338

1339
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
755✔
1340
        if (r < 0)
755✔
1341
                return r;
1342

1343
        return cg_path_get_session(cgroup, ret_session);
755✔
1344
}
1345

1346
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
338✔
1347
        int r;
338✔
1348

1349
        if (!pidref_is_set(pidref))
338✔
1350
                return -ESRCH;
338✔
1351
        if (pidref_is_remote(pidref))
676✔
1352
                return -EREMOTE;
1353

1354
        _cleanup_free_ char *session = NULL;
338✔
1355
        r = cg_pid_get_session(pidref->pid, &session);
338✔
1356
        if (r < 0)
338✔
1357
                return r;
1358

1359
        r = pidref_verify(pidref);
284✔
1360
        if (r < 0)
284✔
1361
                return r;
1362

1363
        if (ret)
284✔
1364
                *ret = TAKE_PTR(session);
284✔
1365
        return 0;
1366
}
1367

1368
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
7,978✔
1369
        _cleanup_free_ char *slice = NULL;
7,978✔
1370
        char *start, *end;
7,978✔
1371
        int r;
7,978✔
1372

1373
        assert(path);
7,978✔
1374

1375
        r = cg_path_get_slice(path, &slice);
7,978✔
1376
        if (r < 0)
7,978✔
1377
                return r;
1378

1379
        start = startswith(slice, "user-");
7,978✔
1380
        if (!start)
7,978✔
1381
                return -ENXIO;
1382

1383
        end = endswith(start, ".slice");
463✔
1384
        if (!end)
463✔
1385
                return -ENXIO;
1386

1387
        *end = 0;
463✔
1388
        if (parse_uid(start, ret_uid) < 0)
463✔
UNCOV
1389
                return -ENXIO;
×
1390

1391
        return 0;
1392
}
1393

1394
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
410✔
1395
        _cleanup_free_ char *cgroup = NULL;
410✔
1396
        int r;
410✔
1397

1398
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
410✔
1399
        if (r < 0)
410✔
1400
                return r;
1401

1402
        return cg_path_get_owner_uid(cgroup, ret_uid);
410✔
1403
}
1404

1405
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
52✔
1406
        int r;
52✔
1407

1408
        if (!pidref_is_set(pidref))
52✔
1409
                return -ESRCH;
52✔
1410
        if (pidref_is_remote(pidref))
52✔
1411
                return -EREMOTE;
1412

1413
        uid_t uid;
52✔
1414
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
52✔
1415
        if (r < 0)
52✔
1416
                return r;
1417

1418
        r = pidref_verify(pidref);
8✔
1419
        if (r < 0)
8✔
1420
                return r;
1421

1422
        if (ret)
8✔
1423
                *ret = uid;
8✔
1424

1425
        return 0;
1426
}
1427

1428
int cg_path_get_slice(const char *p, char **ret_slice) {
15,766✔
1429
        const char *e = NULL;
15,766✔
1430

1431
        assert(p);
15,766✔
1432
        assert(ret_slice);
15,766✔
1433

1434
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1435
         * the first non-slice unit. */
1436

1437
        for (;;) {
48,282✔
1438
                const char *s;
32,024✔
1439
                int n;
32,024✔
1440

1441
                n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s);
32,024✔
1442
                if (n < 0)
32,024✔
UNCOV
1443
                        return n;
×
1444
                if (!valid_slice_name(s, n))
32,024✔
1445
                        break;
1446

1447
                e = s;
16,258✔
1448
        }
1449

1450
        if (e)
15,766✔
1451
                return cg_path_decode_unit(e, ret_slice);
15,537✔
1452

1453
        return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
229✔
1454
}
1455

1456
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
59✔
1457
        _cleanup_free_ char *cgroup = NULL;
59✔
1458
        int r;
59✔
1459

1460
        assert(ret_slice);
59✔
1461

1462
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
59✔
1463
        if (r < 0)
59✔
1464
                return r;
1465

1466
        return cg_path_get_slice(cgroup, ret_slice);
59✔
1467
}
1468

1469
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,527✔
1470
        const char *t;
7,527✔
1471
        assert(p);
7,527✔
1472
        assert(ret_slice);
7,527✔
1473

1474
        t = skip_user_prefix(p);
7,527✔
1475
        if (!t)
7,527✔
1476
                return -ENXIO;
1477

1478
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1479
         * from here on. */
1480
        return cg_path_get_slice(t, ret_slice);
209✔
1481
}
1482

1483
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1484
        _cleanup_free_ char *cgroup = NULL;
1✔
1485
        int r;
1✔
1486

1487
        assert(ret_slice);
1✔
1488

1489
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1✔
1490
        if (r < 0)
1✔
1491
                return r;
1492

1493
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1494
}
1495

1496
bool cg_needs_escape(const char *p) {
12,648✔
1497

1498
        /* Checks if the specified path is a valid cgroup name by our rules, or if it must be escaped. Note
1499
         * that we consider escaped cgroup names invalid here, as they need to be escaped a second time if
1500
         * they shall be used. Also note that various names cannot be made valid by escaping even if we
1501
         * return true here (because too long, or contain the forbidden character "/"). */
1502

1503
        if (!filename_is_valid(p))
12,648✔
1504
                return true;
1505

1506
        if (IN_SET(p[0], '_', '.'))
12,644✔
1507
                return true;
1508

1509
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
12,638✔
1510
                return true;
2✔
1511

1512
        if (startswith(p, "cgroup."))
12,636✔
1513
                return true;
1514

1515
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
176,876✔
1516
                const char *q;
164,242✔
1517

1518
                q = startswith(p, cgroup_controller_to_string(c));
164,242✔
1519
                if (!q)
164,242✔
1520
                        continue;
164,242✔
1521

UNCOV
1522
                if (q[0] == '.')
×
1523
                        return true;
1524
        }
1525

1526
        return false;
1527
}
1528

1529
int cg_escape(const char *p, char **ret) {
12,395✔
1530
        _cleanup_free_ char *n = NULL;
12,395✔
1531

1532
        /* This implements very minimal escaping for names to be used as file names in the cgroup tree: any
1533
         * name which might conflict with a kernel name or is prefixed with '_' is prefixed with a '_'. That
1534
         * way, when reading cgroup names it is sufficient to remove a single prefixing underscore if there
1535
         * is one. */
1536

1537
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1538

1539
        if (cg_needs_escape(p)) {
12,395✔
1540
                n = strjoin("_", p);
7✔
1541
                if (!n)
7✔
1542
                        return -ENOMEM;
1543

1544
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1545
                        return -EINVAL;
1546
        } else {
1547
                n = strdup(p);
12,388✔
1548
                if (!n)
12,388✔
1549
                        return -ENOMEM;
1550
        }
1551

1552
        *ret = TAKE_PTR(n);
12,395✔
1553
        return 0;
12,395✔
1554
}
1555

1556
char* cg_unescape(const char *p) {
100,030✔
1557
        assert(p);
100,030✔
1558

1559
        /* The return value of this function (unlike cg_escape())
1560
         * doesn't need free()! */
1561

1562
        if (p[0] == '_')
100,030✔
1563
                return (char*) p+1;
14✔
1564

1565
        return (char*) p;
1566
}
1567

1568
#define CONTROLLER_VALID                        \
1569
        DIGITS LETTERS                          \
1570
        "_"
1571

1572
bool cg_controller_is_valid(const char *p) {
296,204✔
1573
        const char *t, *s;
296,204✔
1574

1575
        if (!p)
296,204✔
1576
                return false;
1577

1578
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
296,204✔
1579
                return true;
1580

1581
        s = startswith(p, "name=");
91,063✔
1582
        if (s)
91,063✔
1583
                p = s;
2✔
1584

1585
        if (IN_SET(*p, 0, '_'))
91,063✔
1586
                return false;
1587

1588
        for (t = p; *t; t++)
585,114✔
1589
                if (!strchr(CONTROLLER_VALID, *t))
494,062✔
1590
                        return false;
1591

1592
        if (t - p > NAME_MAX)
91,052✔
UNCOV
1593
                return false;
×
1594

1595
        return true;
1596
}
1597

1598
int cg_slice_to_path(const char *unit, char **ret) {
5,019✔
1599
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
5,019✔
1600
        const char *dash;
5,019✔
1601
        int r;
5,019✔
1602

1603
        assert(unit);
5,019✔
1604
        assert(ret);
5,019✔
1605

1606
        if (streq(unit, SPECIAL_ROOT_SLICE))
5,019✔
1607
                return strdup_to(ret, "");
7✔
1608

1609
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
5,012✔
1610
                return -EINVAL;
1611

1612
        if (!endswith(unit, ".slice"))
5,001✔
1613
                return -EINVAL;
1614

1615
        r = unit_name_to_prefix(unit, &p);
5,000✔
1616
        if (r < 0)
5,000✔
1617
                return r;
1618

1619
        dash = strchr(p, '-');
5,000✔
1620

1621
        /* Don't allow initial dashes */
1622
        if (dash == p)
5,000✔
1623
                return -EINVAL;
1624

1625
        while (dash) {
5,160✔
1626
                _cleanup_free_ char *escaped = NULL;
165✔
1627
                char n[dash - p + sizeof(".slice")];
165✔
1628

1629
#if HAS_FEATURE_MEMORY_SANITIZER
1630
                /* msan doesn't instrument stpncpy, so it thinks
1631
                 * n is later used uninitialized:
1632
                 * https://github.com/google/sanitizers/issues/926
1633
                 */
1634
                zero(n);
1635
#endif
1636

1637
                /* Don't allow trailing or double dashes */
1638
                if (IN_SET(dash[1], 0, '-'))
165✔
1639
                        return -EINVAL;
1640

1641
                strcpy(stpncpy(n, p, dash - p), ".slice");
163✔
1642
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
163✔
1643
                        return -EINVAL;
1644

1645
                r = cg_escape(n, &escaped);
163✔
1646
                if (r < 0)
163✔
1647
                        return r;
1648

1649
                if (!strextend(&s, escaped, "/"))
163✔
1650
                        return -ENOMEM;
1651

1652
                dash = strchr(dash+1, '-');
163✔
1653
        }
1654

1655
        r = cg_escape(unit, &e);
4,995✔
1656
        if (r < 0)
4,995✔
1657
                return r;
1658

1659
        if (!strextend(&s, e))
4,995✔
1660
                return -ENOMEM;
1661

1662
        *ret = TAKE_PTR(s);
4,995✔
1663
        return 0;
4,995✔
1664
}
1665

1666
int cg_is_threaded(const char *path) {
×
UNCOV
1667
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
UNCOV
1668
        _cleanup_strv_free_ char **v = NULL;
×
1669
        int r;
×
1670

UNCOV
1671
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.type", &fs);
×
1672
        if (r < 0)
×
1673
                return r;
1674

1675
        r = read_full_virtual_file(fs, &contents, NULL);
×
1676
        if (r == -ENOENT)
×
1677
                return false; /* Assume no. */
UNCOV
1678
        if (r < 0)
×
1679
                return r;
1680

1681
        v = strv_split(contents, NULL);
×
UNCOV
1682
        if (!v)
×
1683
                return -ENOMEM;
1684

1685
        /* If the cgroup is in the threaded mode, it contains "threaded".
1686
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
UNCOV
1687
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1688
}
1689

1690
int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) {
34,365✔
1691
        _cleanup_free_ char *p = NULL;
34,365✔
1692
        int r;
34,365✔
1693

1694
        r = cg_get_path(controller, path, attribute, &p);
34,365✔
1695
        if (r < 0)
34,365✔
1696
                return r;
1697

1698
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER);
34,365✔
1699
}
1700

1701
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) {
28,381✔
1702
        _cleanup_free_ char *p = NULL;
28,381✔
1703
        int r;
28,381✔
1704

1705
        r = cg_get_path(controller, path, attribute, &p);
28,381✔
1706
        if (r < 0)
28,381✔
1707
                return r;
1708

1709
        return read_one_line_file(p, ret);
28,381✔
1710
}
1711

1712
int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret) {
23,859✔
1713
        _cleanup_free_ char *value = NULL;
23,859✔
1714
        uint64_t v;
23,859✔
1715
        int r;
23,859✔
1716

1717
        assert(ret);
23,859✔
1718

1719
        r = cg_get_attribute(controller, path, attribute, &value);
23,859✔
1720
        if (r == -ENOENT)
23,859✔
1721
                return -ENODATA;
1722
        if (r < 0)
22,161✔
1723
                return r;
1724

1725
        if (streq(value, "max")) {
22,161✔
1726
                *ret = CGROUP_LIMIT_MAX;
5,292✔
1727
                return 0;
5,292✔
1728
        }
1729

1730
        r = safe_atou64(value, &v);
16,869✔
1731
        if (r < 0)
16,869✔
1732
                return r;
1733

1734
        *ret = v;
16,869✔
1735
        return 0;
16,869✔
1736
}
1737

1738
int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret) {
65✔
1739
        _cleanup_free_ char *value = NULL;
65✔
1740
        int r;
65✔
1741

1742
        assert(ret);
65✔
1743

1744
        r = cg_get_attribute(controller, path, attribute, &value);
65✔
1745
        if (r == -ENOENT)
65✔
1746
                return -ENODATA;
1747
        if (r < 0)
65✔
1748
                return r;
1749

1750
        r = parse_boolean(value);
65✔
1751
        if (r < 0)
65✔
1752
                return r;
1753

1754
        *ret = r;
65✔
1755
        return 0;
65✔
1756
}
1757

1758
int cg_get_owner(const char *path, uid_t *ret_uid) {
35✔
1759
        _cleanup_free_ char *f = NULL;
35✔
1760
        struct stat stats;
35✔
1761
        int r;
35✔
1762

1763
        assert(ret_uid);
35✔
1764

1765
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &f);
35✔
1766
        if (r < 0)
35✔
1767
                return r;
1768

1769
        if (stat(f, &stats) < 0)
35✔
1770
                return -errno;
16✔
1771

1772
        r = stat_verify_directory(&stats);
19✔
1773
        if (r < 0)
19✔
1774
                return r;
1775

1776
        *ret_uid = stats.st_uid;
19✔
1777
        return 0;
19✔
1778
}
1779

1780
int cg_get_keyed_attribute(
26,769✔
1781
                const char *controller,
1782
                const char *path,
1783
                const char *attribute,
1784
                char * const *keys,
1785
                char **values) {
1786

1787
        _cleanup_free_ char *filename = NULL, *contents = NULL;
26,769✔
1788
        size_t n;
26,769✔
1789
        int r;
26,769✔
1790

1791
        assert(path);
26,769✔
1792
        assert(attribute);
26,769✔
1793

1794
        /* Reads one or more fields of a cgroup v2 keyed attribute file. The 'keys' parameter should be an strv with
1795
         * all keys to retrieve. The 'values' parameter should be passed as string size with the same number of
1796
         * entries as 'keys'. On success each entry will be set to the value of the matching key.
1797
         *
1798
         * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. */
1799

1800
        r = cg_get_path(controller, path, attribute, &filename);
26,769✔
1801
        if (r < 0)
26,769✔
1802
                return r;
1803

1804
        r = read_full_file(filename, &contents, /* ret_size = */ NULL);
26,769✔
1805
        if (r < 0)
26,769✔
1806
                return r;
1807

1808
        n = strv_length(keys);
20,242✔
1809
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
20,242✔
1810
                return 0;
1811
        assert(strv_is_uniq(keys));
20,242✔
1812

1813
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1814
        char **v = newa0(char*, n);
20,242✔
1815
        size_t n_done = 0;
20,242✔
1816

1817
        for (const char *p = contents; *p;) {
69,297✔
1818
                const char *w;
1819
                size_t i;
1820

1821
                for (i = 0; i < n; i++) {
118,352✔
1822
                        w = first_word(p, keys[i]);
76,274✔
1823
                        if (w)
76,274✔
1824
                                break;
1825
                }
1826

1827
                if (w) {
69,297✔
1828
                        if (v[i]) { /* duplicate entry? */
27,219✔
UNCOV
1829
                                r = -EBADMSG;
×
UNCOV
1830
                                goto fail;
×
1831
                        }
1832

1833
                        size_t l = strcspn(w, NEWLINE);
27,219✔
1834

1835
                        v[i] = strndup(w, l);
27,219✔
1836
                        if (!v[i]) {
27,219✔
UNCOV
1837
                                r = -ENOMEM;
×
UNCOV
1838
                                goto fail;
×
1839
                        }
1840

1841
                        n_done++;
27,219✔
1842
                        if (n_done >= n)
27,219✔
1843
                                break;
1844

1845
                        p = w + l;
6,977✔
1846
                } else
1847
                        p += strcspn(p, NEWLINE);
42,078✔
1848

1849
                p += strspn(p, NEWLINE);
49,055✔
1850
        }
1851

1852
        if (n_done < n) {
20,242✔
UNCOV
1853
                r = -ENXIO;
×
1854
                goto fail;
×
1855
        }
1856

1857
        memcpy(values, v, sizeof(char*) * n);
20,242✔
1858
        return 0;
20,242✔
1859

UNCOV
1860
fail:
×
1861
        free_many_charp(v, n);
26,769✔
1862
        return r;
1863
}
1864

1865
int cg_mask_to_string(CGroupMask mask, char **ret) {
11,982✔
1866
        _cleanup_free_ char *s = NULL;
11,982✔
1867
        bool space = false;
11,982✔
1868
        CGroupController c;
11,982✔
1869
        size_t n = 0;
11,982✔
1870

1871
        assert(ret);
11,982✔
1872

1873
        if (mask == 0) {
11,982✔
1874
                *ret = NULL;
4,925✔
1875
                return 0;
4,925✔
1876
        }
1877

1878
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
98,798✔
1879
                const char *k;
91,741✔
1880
                size_t l;
91,741✔
1881

1882
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
91,741✔
1883
                        continue;
32,624✔
1884

1885
                k = cgroup_controller_to_string(c);
59,117✔
1886
                l = strlen(k);
59,117✔
1887

1888
                if (!GREEDY_REALLOC(s, n + space + l + 1))
59,117✔
1889
                        return -ENOMEM;
1890

1891
                if (space)
59,117✔
1892
                        s[n] = ' ';
52,060✔
1893
                memcpy(s + n + space, k, l);
59,117✔
1894
                n += space + l;
59,117✔
1895

1896
                space = true;
59,117✔
1897
        }
1898

1899
        assert(s);
7,057✔
1900

1901
        s[n] = 0;
7,057✔
1902
        *ret = TAKE_PTR(s);
7,057✔
1903

1904
        return 0;
7,057✔
1905
}
1906

1907
int cg_mask_from_string(const char *value, CGroupMask *ret) {
6,467✔
1908
        CGroupMask m = 0;
6,467✔
1909

1910
        assert(ret);
6,467✔
1911
        assert(value);
6,467✔
1912

1913
        for (;;) {
58,145✔
1914
                _cleanup_free_ char *n = NULL;
51,678✔
1915
                CGroupController v;
58,145✔
1916
                int r;
58,145✔
1917

1918
                r = extract_first_word(&value, &n, NULL, 0);
58,145✔
1919
                if (r < 0)
58,145✔
UNCOV
1920
                        return r;
×
1921
                if (r == 0)
58,145✔
1922
                        break;
1923

1924
                v = cgroup_controller_from_string(n);
51,678✔
1925
                if (v < 0)
51,678✔
1926
                        continue;
642✔
1927

1928
                m |= CGROUP_CONTROLLER_TO_MASK(v);
51,036✔
1929
        }
1930

1931
        *ret = m;
6,467✔
1932
        return 0;
6,467✔
1933
}
1934

1935
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
464✔
1936
        CGroupMask mask;
464✔
1937
        int r;
464✔
1938

1939
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1940
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1941
         * pseudo-controllers. */
1942

1943
        r = cg_all_unified();
464✔
1944
        if (r < 0)
464✔
1945
                return r;
464✔
1946
        if (r > 0) {
464✔
1947
                _cleanup_free_ char *controllers = NULL, *path = NULL;
464✔
1948

1949
                /* In the unified hierarchy we can read the supported and accessible controllers from
1950
                 * the top-level cgroup attribute */
1951

1952
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path);
464✔
1953
                if (r < 0)
464✔
1954
                        return r;
1955

1956
                r = read_one_line_file(path, &controllers);
464✔
1957
                if (r < 0)
464✔
1958
                        return r;
1959

1960
                r = cg_mask_from_string(controllers, &mask);
464✔
1961
                if (r < 0)
464✔
1962
                        return r;
1963

1964
                /* Mask controllers that are not supported in unified hierarchy. */
1965
                mask &= CGROUP_MASK_V2;
464✔
1966

1967
        } else {
1968
                CGroupController c;
×
1969

1970
                /* In the legacy hierarchy, we check which hierarchies are accessible. */
1971

1972
                mask = 0;
×
UNCOV
1973
                for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
×
1974
                        CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
×
1975
                        const char *n;
×
1976

UNCOV
1977
                        if (!FLAGS_SET(CGROUP_MASK_V1, bit))
×
UNCOV
1978
                                continue;
×
1979

UNCOV
1980
                        n = cgroup_controller_to_string(c);
×
UNCOV
1981
                        if (controller_is_v1_accessible(root, n) >= 0)
×
UNCOV
1982
                                mask |= bit;
×
1983
                }
1984
        }
1985

1986
        *ret = mask;
464✔
1987
        return 0;
464✔
1988
}
1989

1990
int cg_mask_supported(CGroupMask *ret) {
218✔
1991
        _cleanup_free_ char *root = NULL;
218✔
1992
        int r;
218✔
1993

1994
        r = cg_get_root_path(&root);
218✔
1995
        if (r < 0)
218✔
1996
                return r;
1997

1998
        return cg_mask_supported_subtree(root, ret);
218✔
1999
}
2000

2001
/* The hybrid mode was initially implemented in v232 and simply mounted cgroup2 on
2002
 * /sys/fs/cgroup/systemd. This unfortunately broke other tools (such as docker) which expected the v1
2003
 * "name=systemd" hierarchy on /sys/fs/cgroup/systemd. From v233 and on, the hybrid mode mounts v2 on
2004
 * /sys/fs/cgroup/unified and maintains "name=systemd" hierarchy on /sys/fs/cgroup/systemd for compatibility
2005
 * with other tools.
2006
 *
2007
 * To keep live upgrade working, we detect and support v232 layout. When v232 layout is detected, to keep
2008
 * cgroup v2 process management but disable the compat dual layout, we return true on
2009
 * cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) and false on cg_hybrid_unified().
2010
 */
2011
static thread_local bool unified_systemd_v232;
2012

2013
int cg_unified_cached(bool flush) {
308,555✔
2014
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
308,555✔
2015

2016
        struct statfs fs;
308,555✔
2017

2018
        /* Checks if we support the unified hierarchy. Returns an
2019
         * error when the cgroup hierarchies aren't mounted yet or we
2020
         * have any other trouble determining if the unified hierarchy
2021
         * is supported. */
2022

2023
        if (flush)
308,555✔
2024
                unified_cache = CGROUP_UNIFIED_UNKNOWN;
4✔
2025
        else if (unified_cache >= CGROUP_UNIFIED_NONE)
308,551✔
2026
                return unified_cache;
308,555✔
2027

2028
        if (statfs("/sys/fs/cgroup/", &fs) < 0)
12,969✔
2029
                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/\") failed: %m");
×
2030

2031
        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
12,969✔
2032
                log_debug("Found cgroup2 on /sys/fs/cgroup/, full unified hierarchy");
12,969✔
2033
                unified_cache = CGROUP_UNIFIED_ALL;
12,969✔
UNCOV
2034
        } else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) {
×
2035
                if (statfs("/sys/fs/cgroup/unified/", &fs) == 0 &&
×
2036
                    F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
×
UNCOV
2037
                        log_debug("Found cgroup2 on /sys/fs/cgroup/unified, unified hierarchy for systemd controller");
×
2038
                        unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
2039
                        unified_systemd_v232 = false;
×
2040
                } else {
2041
                        if (statfs("/sys/fs/cgroup/systemd/", &fs) < 0) {
×
UNCOV
2042
                                if (errno == ENOENT) {
×
2043
                                        /* Some other software may have set up /sys/fs/cgroup in a configuration we do not recognize. */
2044
                                        log_debug_errno(errno, "Unsupported cgroupsv1 setup detected: name=systemd hierarchy not found.");
×
2045
                                        return -ENOMEDIUM;
×
2046
                                }
2047
                                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/systemd\" failed: %m");
×
2048
                        }
2049

2050
                        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
×
UNCOV
2051
                                log_debug("Found cgroup2 on /sys/fs/cgroup/systemd, unified hierarchy for systemd controller (v232 variant)");
×
2052
                                unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
UNCOV
2053
                                unified_systemd_v232 = true;
×
2054
                        } else if (F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) {
×
UNCOV
2055
                                log_debug("Found cgroup on /sys/fs/cgroup/systemd, legacy hierarchy");
×
UNCOV
2056
                                unified_cache = CGROUP_UNIFIED_NONE;
×
2057
                        } else {
2058
                                log_debug("Unexpected filesystem type %llx mounted on /sys/fs/cgroup/systemd, assuming legacy hierarchy",
×
2059
                                          (unsigned long long) fs.f_type);
UNCOV
2060
                                unified_cache = CGROUP_UNIFIED_NONE;
×
2061
                        }
2062
                }
UNCOV
2063
        } else if (F_TYPE_EQUAL(fs.f_type, SYSFS_MAGIC)) {
×
UNCOV
2064
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
2065
                                       "No filesystem is currently mounted on /sys/fs/cgroup.");
2066
        } else
UNCOV
2067
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
2068
                                       "Unknown filesystem type %llx mounted on /sys/fs/cgroup.",
2069
                                       (unsigned long long)fs.f_type);
2070

2071
        return unified_cache;
12,969✔
2072
}
2073

2074
int cg_unified_controller(const char *controller) {
48,016✔
2075
        int r;
48,016✔
2076

2077
        r = cg_unified_cached(false);
48,016✔
2078
        if (r < 0)
48,016✔
2079
                return r;
2080

2081
        if (r == CGROUP_UNIFIED_NONE)
48,016✔
2082
                return false;
2083

2084
        if (r >= CGROUP_UNIFIED_ALL)
48,016✔
2085
                return true;
2086

UNCOV
2087
        return streq_ptr(controller, SYSTEMD_CGROUP_CONTROLLER);
×
2088
}
2089

2090
int cg_all_unified(void) {
260,533✔
2091
        int r;
260,533✔
2092

2093
        r = cg_unified_cached(false);
260,533✔
2094
        if (r < 0)
260,533✔
2095
                return r;
2096

2097
        return r >= CGROUP_UNIFIED_ALL;
260,533✔
2098
}
2099

2100
int cg_hybrid_unified(void) {
1✔
2101
        int r;
1✔
2102

2103
        r = cg_unified_cached(false);
1✔
2104
        if (r < 0)
1✔
2105
                return r;
2106

2107
        return r == CGROUP_UNIFIED_SYSTEMD && !unified_systemd_v232;
1✔
2108
}
2109

2110
int cg_is_delegated(const char *path) {
19✔
2111
        int r;
19✔
2112

2113
        assert(path);
19✔
2114

2115
        r = cg_get_xattr_bool(path, "trusted.delegate");
19✔
2116
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
19✔
2117
                return r;
2118

2119
        /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the assumption
2120
         * that whoever is trusted enough to own the cgroup, is also trusted enough to decide if it is
2121
         * delegated or not this should be safe. */
2122
        r = cg_get_xattr_bool(path, "user.delegate");
6✔
2123
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
6✔
2124
}
2125

2126
int cg_is_delegated_fd(int fd) {
214✔
2127
        int r;
214✔
2128

2129
        assert(fd >= 0);
214✔
2130

2131
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
214✔
2132
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
214✔
2133
                return r;
2134

2135
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
200✔
2136
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
200✔
2137
}
2138

2139
int cg_has_coredump_receive(const char *path) {
2✔
2140
        int r;
2✔
2141

2142
        assert(path);
2✔
2143

2144
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
2145
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
UNCOV
2146
                return false;
×
2147

2148
        return r;
2149
}
2150

2151
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2152
        [CGROUP_IO_RBPS_MAX]    = CGROUP_LIMIT_MAX,
2153
        [CGROUP_IO_WBPS_MAX]    = CGROUP_LIMIT_MAX,
2154
        [CGROUP_IO_RIOPS_MAX]   = CGROUP_LIMIT_MAX,
2155
        [CGROUP_IO_WIOPS_MAX]   = CGROUP_LIMIT_MAX,
2156
};
2157

2158
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2159
        [CGROUP_IO_RBPS_MAX]    = "IOReadBandwidthMax",
2160
        [CGROUP_IO_WBPS_MAX]    = "IOWriteBandwidthMax",
2161
        [CGROUP_IO_RIOPS_MAX]   = "IOReadIOPSMax",
2162
        [CGROUP_IO_WIOPS_MAX]   = "IOWriteIOPSMax",
2163
};
2164

2165
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
5,240✔
2166

2167
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
2168
        [CGROUP_CONTROLLER_CPU] = "cpu",
2169
        [CGROUP_CONTROLLER_CPUACCT] = "cpuacct",
2170
        [CGROUP_CONTROLLER_CPUSET] = "cpuset",
2171
        [CGROUP_CONTROLLER_IO] = "io",
2172
        [CGROUP_CONTROLLER_BLKIO] = "blkio",
2173
        [CGROUP_CONTROLLER_MEMORY] = "memory",
2174
        [CGROUP_CONTROLLER_DEVICES] = "devices",
2175
        [CGROUP_CONTROLLER_PIDS] = "pids",
2176
        [CGROUP_CONTROLLER_BPF_FIREWALL] = "bpf-firewall",
2177
        [CGROUP_CONTROLLER_BPF_DEVICES] = "bpf-devices",
2178
        [CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
2179
        [CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind",
2180
        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
2181
};
2182

2183
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
296,264✔
2184

2185
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
2186
        [MANAGED_OOM_AUTO] = "auto",
2187
        [MANAGED_OOM_KILL] = "kill",
2188
};
2189

2190
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
31,401✔
2191

2192
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
2193
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
2194
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
2195
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
2196
};
2197

2198
DEFINE_STRING_TABLE_LOOKUP(managed_oom_preference, ManagedOOMPreference);
15,428✔
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