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

systemd / systemd / 16791678039

06 Aug 2025 11:10PM UTC coverage: 72.181% (-0.04%) from 72.223%
16791678039

push

github

yuwata
logging: Improve logging messages related to NFTSet.

The 'NFTSet' directive in various units adds and removes entries in nftables
sets, it does not add or remove entire sets. The logging messages should
indicate that an entry was added or removed, not that a set was added or
removed.

2 of 6 new or added lines in 3 files covered. (33.33%)

496 existing lines in 52 files now uncovered.

302228 of 418708 relevant lines covered (72.18%)

647735.83 hits per line

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

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

3
#include <linux/fs.h>
4
#include <linux/magic.h>
5
#include <signal.h>
6
#include <stdlib.h>
7
#include <sys/xattr.h>
8
#include <threads.h>
9
#include <unistd.h>
10

11
#include "alloc-util.h"
12
#include "capsule-util.h"
13
#include "cgroup-util.h"
14
#include "dirent-util.h"
15
#include "errno-util.h"
16
#include "extract-word.h"
17
#include "fd-util.h"
18
#include "fileio.h"
19
#include "format-util.h"
20
#include "fs-util.h"
21
#include "log.h"
22
#include "login-util.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
} _alignas_(uint64_t) 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) {
827✔
52
        _cleanup_free_ char *fs = NULL;
827✔
53
        int r;
827✔
54

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

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

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

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

70
                cgroupfs_fd = fsfd;
71
        }
72

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

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

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

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

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

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

95
        if (ret)
×
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,654✔
101
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
4,654✔
102
        int mnt_id;
4,654✔
103

104
        assert(dfd >= 0 || (dfd == AT_FDCWD && path_is_absolute(path)));
9,281✔
105
        assert(ret);
4,654✔
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) {
9,308✔
110
                assert(errno != EOVERFLOW);
×
111
                return -errno;
×
112
        }
113

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

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

123
        assert(ret);
14,003✔
124

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

129
        f = fopen(fs, "re");
14,003✔
130
        if (!f)
14,003✔
131
                return -errno;
8,943✔
132

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

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

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

142
        assert(f);
9,221✔
143
        assert(ret);
9,221✔
144

145
        /* NB: The kernel returns ENODEV if we tried to read from cgroup.procs of a cgroup that has been
146
         * removed already. Callers should handle that! */
147

148
        for (;;) {
9,221✔
149
                errno = 0;
9,221✔
150
                if (fscanf(f, "%lu", &ul) != 1) {
9,221✔
151

152
                        if (feof(f)) {
5,275✔
153
                                *ret = 0;
5,275✔
154
                                return 0;
5,275✔
155
                        }
156

157
                        return errno_or_else(EIO);
×
158
                }
159

160
                if (ul > PID_T_MAX)
3,946✔
161
                        return -EIO;
162

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

169
                *ret = (pid_t) ul;
3,946✔
170
                return 1;
3,946✔
171
        }
172
}
173

174
int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) {
6,414✔
175
        int r;
6,414✔
176

177
        assert(f);
6,414✔
178
        assert(ret);
6,414✔
179

180
        for (;;) {
×
181
                pid_t pid;
6,414✔
182

183
                r = cg_read_pid(f, &pid, flags);
6,414✔
184
                if (r < 0)
6,414✔
185
                        return log_debug_errno(r, "Failed to read pid from cgroup item: %m");
×
186
                if (r == 0) {
6,414✔
187
                        *ret = PIDREF_NULL;
4,689✔
188
                        return 0;
4,689✔
189
                }
190

191
                if (pid == 0)
1,725✔
192
                        return -EREMOTE;
193

194
                r = pidref_set_pid(ret, pid);
1,725✔
195
                if (r >= 0)
1,725✔
196
                        return 1;
197
                if (r != -ESRCH)
×
198
                        return r;
199

200
                /* ESRCH → gone by now? just skip over it, read the next */
201
        }
202
}
203

UNCOV
204
bool cg_kill_supported(void) {
×
UNCOV
205
        static thread_local int supported = -1;
×
206

UNCOV
207
        if (supported >= 0)
×
208
                return supported;
×
209

UNCOV
210
        if (cg_all_unified() <= 0)
×
211
                return (supported = false);
×
212

UNCOV
213
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
×
UNCOV
214
                return (supported = true);
×
215
        if (errno != ENOENT)
×
216
                log_debug_errno(errno, "Failed to check whether cgroup.kill is available, assuming not: %m");
×
217
        return (supported = false);
×
218
}
219

220
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret) {
13,583✔
221
        _cleanup_free_ char *fs = NULL;
13,583✔
222
        DIR *d;
13,583✔
223
        int r;
13,583✔
224

225
        assert(ret);
13,583✔
226

227
        /* This is not recursive! */
228

229
        r = cg_get_path(controller, path, NULL, &fs);
13,583✔
230
        if (r < 0)
13,583✔
231
                return r;
232

233
        d = opendir(fs);
13,583✔
234
        if (!d)
13,583✔
235
                return -errno;
8,943✔
236

237
        *ret = d;
4,640✔
238
        return 0;
4,640✔
239
}
240

241
int cg_read_subgroup(DIR *d, char **ret) {
6,003✔
242
        assert(d);
6,003✔
243
        assert(ret);
6,003✔
244

245
        FOREACH_DIRENT_ALL(de, d, return -errno) {
223,443✔
246
                if (de->d_type != DT_DIR)
218,588✔
247
                        continue;
207,730✔
248

249
                if (dot_or_dot_dot(de->d_name))
10,858✔
250
                        continue;
9,710✔
251

252
                return strdup_to_full(ret, de->d_name);
1,148✔
253
        }
254

255
        *ret = NULL;
4,855✔
256
        return 0;
4,855✔
257
}
258

259
int cg_kill(
13,499✔
260
                const char *path,
261
                int sig,
262
                CGroupFlags flags,
263
                Set *killed_pids,
264
                cg_kill_log_func_t log_kill,
265
                void *userdata) {
266

267
        _cleanup_set_free_ Set *allocated_set = NULL;
13,499✔
268
        int r, ret = 0;
13,499✔
269

270
        assert(path);
13,499✔
271
        assert(sig >= 0);
13,499✔
272

273
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
274
          * don't send SIGCONT on SIGKILL. */
275
        if (IN_SET(sig, SIGCONT, SIGKILL))
13,499✔
276
                flags &= ~CGROUP_SIGCONT;
1,952✔
277

278
        /* This goes through the tasks list and kills them all. This is repeated until no further processes
279
         * are added to the tasks list, to properly handle forking processes.
280
         *
281
         * When sending SIGKILL, prefer cg_kill_kernel_sigkill(), which is fully atomic. */
282

283
        if (!killed_pids) {
13,499✔
284
                killed_pids = allocated_set = set_new(NULL);
660✔
285
                if (!killed_pids)
660✔
286
                        return -ENOMEM;
287
        }
288

289
        bool done;
13,593✔
290
        do {
13,593✔
291
                _cleanup_fclose_ FILE *f = NULL;
8,943✔
292
                int ret_log_kill;
13,593✔
293

294
                done = true;
13,593✔
295

296
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f);
13,593✔
297
                if (r == -ENOENT)
13,593✔
298
                        break;
299
                if (r < 0)
4,650✔
300
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
301

302
                for (;;) {
6,275✔
303
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
6,275✔
304

305
                        r = cg_read_pidref(f, &pidref, flags);
6,275✔
306
                        if (r == -ENODEV) {
6,275✔
307
                                /* reading from cgroup.pids will result in ENODEV if the cgroup is
308
                                 * concurrently removed. Just leave in that case, because a removed cgroup
309
                                 * contains no processes anymore. */
310
                                done = true;
311
                                break;
312
                        }
313
                        if (r < 0)
6,275✔
314
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
315
                        if (r == 0)
6,275✔
316
                                break;
317

318
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
1,625✔
319
                                continue;
661✔
320

321
                        if (set_contains(killed_pids, PID_TO_PTR(pidref.pid)))
964✔
322
                                continue;
676✔
323

324
                        /* Ignore kernel threads to mimic the behavior of cgroup.kill. */
325
                        if (pidref_is_kernel_thread(&pidref) > 0) {
288✔
326
                                log_debug("Ignoring kernel thread with pid " PID_FMT " in cgroup '%s'", pidref.pid, path);
×
327
                                continue;
×
328
                        }
329

330
                        if (log_kill)
288✔
331
                                ret_log_kill = log_kill(&pidref, sig, userdata);
96✔
332

333
                        /* If we haven't killed this process yet, kill it */
334
                        r = pidref_kill(&pidref, sig);
288✔
335
                        if (r < 0 && r != -ESRCH)
288✔
336
                                RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path));
×
337
                        if (r >= 0) {
288✔
338
                                if (flags & CGROUP_SIGCONT)
288✔
339
                                        (void) pidref_kill(&pidref, SIGCONT);
180✔
340

341
                                if (ret == 0) {
288✔
342
                                        if (log_kill)
168✔
343
                                                ret = ret_log_kill;
344
                                        else
345
                                                ret = 1;
72✔
346
                                }
347
                        }
348

349
                        done = false;
288✔
350

351
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
288✔
352
                        if (r < 0)
288✔
353
                                return RET_GATHER(ret, r);
×
354
                }
355

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

359
        } while (!done);
4,650✔
360

361
        return ret;
362
}
363

364
int cg_kill_recursive(
12,837✔
365
                const char *path,
366
                int sig,
367
                CGroupFlags flags,
368
                Set *killed_pids,
369
                cg_kill_log_func_t log_kill,
370
                void *userdata) {
371

372
        _cleanup_set_free_ Set *allocated_set = NULL;
×
373
        _cleanup_closedir_ DIR *d = NULL;
12,837✔
374
        int r, ret;
12,837✔
375

376
        assert(path);
12,837✔
377
        assert(sig >= 0);
12,837✔
378

379
        if (!killed_pids) {
12,837✔
380
                killed_pids = allocated_set = set_new(NULL);
12,365✔
381
                if (!killed_pids)
12,365✔
382
                        return -ENOMEM;
383
        }
384

385
        ret = cg_kill(path, sig, flags, killed_pids, log_kill, userdata);
12,837✔
386

387
        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d);
12,837✔
388
        if (r < 0) {
12,837✔
389
                if (r != -ENOENT)
8,943✔
390
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
391

392
                return ret;
8,943✔
393
        }
394

395
        for (;;) {
4,110✔
396
                _cleanup_free_ char *fn = NULL, *p = NULL;
4,002✔
397

398
                r = cg_read_subgroup(d, &fn);
4,002✔
399
                if (r < 0) {
4,002✔
400
                        RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path));
×
401
                        break;
402
                }
403
                if (r == 0)
4,002✔
404
                        break;
405

406
                p = path_join(empty_to_root(path), fn);
108✔
407
                if (!p)
108✔
408
                        return -ENOMEM;
×
409

410
                r = cg_kill_recursive(p, sig, flags, killed_pids, log_kill, userdata);
108✔
411
                if (r < 0)
108✔
412
                        log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p);
×
413
                if (r != 0 && ret >= 0)
108✔
414
                        ret = r;
16✔
415
        }
416

417
        return ret;
3,894✔
418
}
419

UNCOV
420
int cg_kill_kernel_sigkill(const char *path) {
×
UNCOV
421
        _cleanup_free_ char *killfile = NULL;
×
UNCOV
422
        int r;
×
423

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

UNCOV
427
        assert(path);
×
428

UNCOV
429
        if (!cg_kill_supported())
×
430
                return -EOPNOTSUPP;
431

UNCOV
432
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.kill", &killfile);
×
UNCOV
433
        if (r < 0)
×
434
                return r;
435

UNCOV
436
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
×
UNCOV
437
        if (r < 0)
×
438
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
439

440
        return 0;
441
}
442

443
static const char *controller_to_dirname(const char *controller) {
×
444
        assert(controller);
×
445

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

450
        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
×
451
                if (cg_hybrid_unified() > 0)
×
452
                        controller = SYSTEMD_CGROUP_CONTROLLER_HYBRID;
453
                else
454
                        controller = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
×
455
        }
456

457
        return startswith(controller, "name=") ?: controller;
×
458
}
459

460
static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **ret) {
×
461
        const char *dn;
×
462
        char *t = NULL;
×
463

464
        assert(ret);
×
465
        assert(controller);
×
466

467
        dn = controller_to_dirname(controller);
×
468

469
        if (isempty(path) && isempty(suffix))
×
470
                t = path_join("/sys/fs/cgroup", dn);
×
471
        else if (isempty(path))
×
472
                t = path_join("/sys/fs/cgroup", dn, suffix);
×
473
        else if (isempty(suffix))
×
474
                t = path_join("/sys/fs/cgroup", dn, path);
×
475
        else
476
                t = path_join("/sys/fs/cgroup", dn, path, suffix);
×
477
        if (!t)
×
478
                return -ENOMEM;
479

480
        *ret = t;
×
481
        return 0;
×
482
}
483

484
static int join_path_unified(const char *path, const char *suffix, char **ret) {
223,268✔
485
        char *t;
223,268✔
486

487
        assert(ret);
223,268✔
488

489
        if (isempty(path) && isempty(suffix))
236,668✔
490
                t = strdup("/sys/fs/cgroup");
1,363✔
491
        else if (isempty(path))
221,905✔
492
                t = path_join("/sys/fs/cgroup", suffix);
12,037✔
493
        else if (isempty(suffix))
209,868✔
494
                t = path_join("/sys/fs/cgroup", path);
82,852✔
495
        else
496
                t = path_join("/sys/fs/cgroup", path, suffix);
127,016✔
497
        if (!t)
223,268✔
498
                return -ENOMEM;
499

500
        *ret = t;
223,268✔
501
        return 0;
223,268✔
502
}
503

504
int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret) {
223,514✔
505
        int r;
223,514✔
506

507
        assert(ret);
223,514✔
508

509
        if (!controller) {
223,514✔
510
                char *t;
246✔
511

512
                /* If no controller is specified, we return the path *below* the controllers, without any
513
                 * prefix. */
514

515
                if (isempty(path) && isempty(suffix))
246✔
516
                        return -EINVAL;
517

518
                if (isempty(suffix))
246✔
519
                        t = strdup(path);
×
520
                else if (isempty(path))
246✔
521
                        t = strdup(suffix);
×
522
                else
523
                        t = path_join(path, suffix);
246✔
524
                if (!t)
246✔
525
                        return -ENOMEM;
526

527
                *ret = path_simplify(t);
246✔
528
                return 0;
246✔
529
        }
530

531
        if (!cg_controller_is_valid(controller))
223,268✔
532
                return -EINVAL;
533

534
        r = cg_all_unified();
223,268✔
535
        if (r < 0)
223,268✔
536
                return r;
537
        if (r > 0)
223,268✔
538
                r = join_path_unified(path, suffix, ret);
223,268✔
539
        else
540
                r = join_path_legacy(controller, path, suffix, ret);
×
541
        if (r < 0)
223,268✔
542
                return r;
543

544
        path_simplify(*ret);
223,268✔
545
        return 0;
223,268✔
546
}
547

548
static int controller_is_v1_accessible(const char *root, const char *controller) {
×
549
        const char *cpath, *dn;
×
550

551
        assert(controller);
×
552

553
        dn = controller_to_dirname(controller);
×
554

555
        /* If root if specified, we check that:
556
         * - possible subcgroup is created at root,
557
         * - we can modify the hierarchy. */
558

559
        cpath = strjoina("/sys/fs/cgroup/", dn, root, root ? "/cgroup.procs" : NULL);
×
560
        return access_nofollow(cpath, root ? W_OK : F_OK);
×
561
}
562

563
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret) {
19,819✔
564
        int r;
19,819✔
565

566
        assert(controller);
19,819✔
567
        assert(ret);
19,819✔
568

569
        if (!cg_controller_is_valid(controller))
19,819✔
570
                return -EINVAL;
571

572
        r = cg_all_unified();
19,819✔
573
        if (r < 0)
19,819✔
574
                return r;
575
        if (r > 0) {
19,819✔
576
                /* In the unified hierarchy all controllers are considered accessible,
577
                 * except for the named hierarchies */
578
                if (startswith(controller, "name="))
19,819✔
579
                        return -EOPNOTSUPP;
580
        } else {
581
                /* Check if the specified controller is actually accessible */
582
                r = controller_is_v1_accessible(NULL, controller);
×
583
                if (r < 0)
×
584
                        return r;
585
        }
586

587
        return cg_get_path(controller, path, suffix, ret);
19,819✔
588
}
589

590
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
5,937✔
591
        _cleanup_free_ char *fs = NULL;
5,937✔
592
        int r;
5,937✔
593

594
        assert(path);
5,937✔
595
        assert(name);
5,937✔
596
        assert(value || size <= 0);
5,937✔
597

598
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
5,937✔
599
        if (r < 0)
5,937✔
600
                return r;
601

602
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
5,937✔
603
}
604

605
int cg_get_xattr(const char *path, const char *name, char **ret, size_t *ret_size) {
15,949✔
606
        _cleanup_free_ char *fs = NULL;
15,949✔
607
        int r;
15,949✔
608

609
        assert(path);
15,949✔
610
        assert(name);
15,949✔
611

612
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
15,949✔
613
        if (r < 0)
15,949✔
614
                return r;
615

616
        return lgetxattr_malloc(fs, name, ret, ret_size);
15,949✔
617
}
618

619
int cg_get_xattr_bool(const char *path, const char *name) {
157✔
620
        _cleanup_free_ char *fs = NULL;
157✔
621
        int r;
157✔
622

623
        assert(path);
157✔
624
        assert(name);
157✔
625

626
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
157✔
627
        if (r < 0)
157✔
628
                return r;
629

630
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
157✔
631
}
632

633
int cg_remove_xattr(const char *path, const char *name) {
29,455✔
634
        _cleanup_free_ char *fs = NULL;
29,455✔
635
        int r;
29,455✔
636

637
        assert(path);
29,455✔
638
        assert(name);
29,455✔
639

640
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
29,455✔
641
        if (r < 0)
29,455✔
642
                return r;
643

644
        return RET_NERRNO(removexattr(fs, name));
58,910✔
645
}
646

647
int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
40,228✔
648
        _cleanup_fclose_ FILE *f = NULL;
40,228✔
649
        const char *fs, *controller_str = NULL;  /* avoid false maybe-uninitialized warning */
40,228✔
650
        int unified, r;
40,228✔
651

652
        assert(pid >= 0);
40,228✔
653
        assert(ret_path);
40,228✔
654

655
        if (controller) {
40,228✔
656
                if (!cg_controller_is_valid(controller))
40,039✔
657
                        return -EINVAL;
658
        } else
659
                controller = SYSTEMD_CGROUP_CONTROLLER;
660

661
        unified = cg_unified_controller(controller);
40,228✔
662
        if (unified < 0)
40,228✔
663
                return unified;
664
        if (unified == 0) {
40,228✔
665
                if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
×
666
                        controller_str = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
667
                else
668
                        controller_str = controller;
×
669
        }
670

671
        fs = procfs_file_alloca(pid, "cgroup");
46,372✔
672
        r = fopen_unlocked(fs, "re", &f);
40,228✔
673
        if (r == -ENOENT)
40,228✔
674
                return -ESRCH;
675
        if (r < 0)
36,135✔
676
                return r;
677

678
        for (;;) {
36,135✔
679
                _cleanup_free_ char *line = NULL;
36,135✔
680
                char *e;
36,135✔
681

682
                r = read_line(f, LONG_LINE_MAX, &line);
36,135✔
683
                if (r < 0)
36,135✔
684
                        return r;
685
                if (r == 0)
36,133✔
686
                        return -ENODATA;
687

688
                if (unified) {
36,133✔
689
                        e = startswith(line, "0:");
36,133✔
690
                        if (!e)
36,133✔
691
                                continue;
×
692

693
                        e = strchr(e, ':');
36,133✔
694
                        if (!e)
36,133✔
695
                                continue;
×
696
                } else {
697
                        char *l;
×
698

699
                        l = strchr(line, ':');
×
700
                        if (!l)
×
701
                                continue;
×
702

703
                        l++;
×
704
                        e = strchr(l, ':');
×
705
                        if (!e)
×
706
                                continue;
×
707
                        *e = 0;
×
708

709
                        assert(controller_str);
×
710
                        r = string_contains_word(l, ",", controller_str);
×
711
                        if (r < 0)
×
712
                                return r;
713
                        if (r == 0)
×
714
                                continue;
×
715
                }
716

717
                _cleanup_free_ char *path = strdup(e + 1);
36,133✔
718
                if (!path)
36,133✔
719
                        return -ENOMEM;
720

721
                /* Refuse cgroup paths from outside our cgroup namespace */
722
                if (startswith(path, "/../"))
36,133✔
723
                        return -EUNATCH;
724

725
                /* Truncate suffix indicating the process is a zombie */
726
                e = endswith(path, " (deleted)");
36,133✔
727
                if (e)
36,133✔
728
                        *e = 0;
147✔
729

730
                *ret_path = TAKE_PTR(path);
36,133✔
731
                return 0;
36,133✔
732
        }
733
}
734

735
int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_path) {
9,777✔
736
        _cleanup_free_ char *path = NULL;
9,777✔
737
        int r;
9,777✔
738

739
        assert(ret_path);
9,777✔
740

741
        if (!pidref_is_set(pidref))
9,777✔
742
                return -ESRCH;
743
        if (pidref_is_remote(pidref))
19,554✔
744
                return -EREMOTE;
745

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

750
        r = cg_pid_get_path(controller, pidref->pid, &path);
9,777✔
751
        if (r < 0)
9,777✔
752
                return r;
753

754
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
755
        r = pidref_verify(pidref);
9,775✔
756
        if (r < 0)
9,775✔
757
                return r;
758

759
        *ret_path = TAKE_PTR(path);
9,775✔
760
        return 0;
9,775✔
761
}
762

763
int cg_is_empty(const char *controller, const char *path) {
2,377✔
764
        _cleanup_free_ char *t = NULL;
2,377✔
765
        int r;
2,377✔
766

767
        /* Check if the cgroup hierarchy under 'path' is empty. On cgroup v2 it's exposed via the "populated"
768
         * attribute of "cgroup.events". */
769

770
        assert(path);
2,377✔
771

772
        /* The root cgroup is always populated */
773
        if (empty_or_root(path))
2,377✔
774
                return false;
775

776
        r = cg_get_keyed_attribute(controller, path, "cgroup.events", STRV_MAKE("populated"), &t);
2,377✔
777
        if (r == -ENOENT)
2,377✔
778
                return true;
779
        if (r < 0)
278✔
780
                return r;
781

782
        return streq(t, "0");
278✔
783
}
784

785
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
23✔
786
        _cleanup_free_ char *controller = NULL, *path = NULL;
23✔
787
        int r;
23✔
788

789
        assert(spec);
23✔
790

791
        if (*spec == '/') {
23✔
792
                if (!path_is_normalized(spec))
15✔
793
                        return -EINVAL;
794

795
                if (ret_path) {
15✔
796
                        r = path_simplify_alloc(spec, &path);
15✔
797
                        if (r < 0)
15✔
798
                                return r;
799
                }
800

801
        } else {
802
                const char *e;
8✔
803

804
                e = strchr(spec, ':');
8✔
805
                if (e) {
8✔
806
                        controller = strndup(spec, e-spec);
6✔
807
                        if (!controller)
6✔
808
                                return -ENOMEM;
809
                        if (!cg_controller_is_valid(controller))
6✔
810
                                return -EINVAL;
811

812
                        if (!isempty(e + 1)) {
3✔
813
                                path = strdup(e+1);
2✔
814
                                if (!path)
2✔
815
                                        return -ENOMEM;
816

817
                                if (!path_is_normalized(path) ||
2✔
818
                                    !path_is_absolute(path))
2✔
819
                                        return -EINVAL;
820

821
                                path_simplify(path);
1✔
822
                        }
823

824
                } else {
825
                        if (!cg_controller_is_valid(spec))
2✔
826
                                return -EINVAL;
827

828
                        if (ret_controller) {
1✔
829
                                controller = strdup(spec);
1✔
830
                                if (!controller)
1✔
831
                                        return -ENOMEM;
832
                        }
833
                }
834
        }
835

836
        if (ret_controller)
18✔
837
                *ret_controller = TAKE_PTR(controller);
18✔
838
        if (ret_path)
18✔
839
                *ret_path = TAKE_PTR(path);
18✔
840
        return 0;
841
}
842

843
int cg_mangle_path(const char *path, char **ret) {
435✔
844
        _cleanup_free_ char *c = NULL, *p = NULL;
435✔
845
        int r;
435✔
846

847
        assert(path);
435✔
848
        assert(ret);
435✔
849

850
        /* First, check if it already is a filesystem path */
851
        if (path_startswith(path, "/sys/fs/cgroup"))
435✔
852
                return path_simplify_alloc(path, ret);
431✔
853

854
        /* Otherwise, treat it as cg spec */
855
        r = cg_split_spec(path, &c, &p);
4✔
856
        if (r < 0)
4✔
857
                return r;
858

859
        return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, ret);
8✔
860
}
861

862
int cg_get_root_path(char **ret_path) {
13,008✔
863
        char *p, *e;
13,008✔
864
        int r;
13,008✔
865

866
        assert(ret_path);
13,008✔
867

868
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
13,008✔
869
        if (r < 0)
13,008✔
870
                return r;
13,008✔
871

872
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
13,008✔
873
        if (e)
13,008✔
874
                *e = 0;
12,977✔
875

876
        *ret_path = p;
13,008✔
877
        return 0;
13,008✔
878
}
879

880
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
11,991✔
881
        int r;
11,991✔
882

883
        assert(cgroup);
11,991✔
884
        assert(ret_shifted);
11,991✔
885

886
        _cleanup_free_ char *rt = NULL;
11,991✔
887
        if (!root) {
11,991✔
888
                /* If the root was specified let's use that, otherwise
889
                 * let's determine it from PID 1 */
890

891
                r = cg_get_root_path(&rt);
1,963✔
892
                if (r < 0)
1,963✔
893
                        return r;
894

895
                root = rt;
1,963✔
896
        }
897

898
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
11,991✔
899
        return 0;
11,991✔
900
}
901

902
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
15,908✔
903
        _cleanup_free_ char *raw = NULL;
15,908✔
904
        const char *c;
15,908✔
905
        int r;
15,908✔
906

907
        assert(pid >= 0);
15,908✔
908
        assert(ret_cgroup);
15,908✔
909

910
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw);
15,908✔
911
        if (r < 0)
15,908✔
912
                return r;
913

914
        r = cg_shift_path(raw, root, &c);
11,815✔
915
        if (r < 0)
11,815✔
916
                return r;
917

918
        if (c == raw) {
11,815✔
919
                *ret_cgroup = TAKE_PTR(raw);
11,815✔
920
                return 0;
11,815✔
921
        }
922

923
        return strdup_to(ret_cgroup, c);
×
924
}
925

926
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
33,905✔
927
        assert(cgroup);
33,905✔
928

929
        size_t n = strcspn(cgroup, "/");
33,905✔
930
        if (n < 3)
33,905✔
931
                return -ENXIO;
932

933
        char *c = strndupa_safe(cgroup, n);
33,882✔
934
        c = cg_unescape(c);
33,882✔
935

936
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
33,882✔
937
                return -ENXIO;
938

939
        if (ret_unit)
33,873✔
940
                return strdup_to(ret_unit, c);
33,873✔
941

942
        return 0;
943
}
944

945
static bool valid_slice_name(const char *p, size_t n) {
121,434✔
946
        assert(p || n == 0);
121,434✔
947

948
        if (n < STRLEN("x.slice"))
121,434✔
949
                return false;
950

951
        char *c = strndupa_safe(p, n);
121,371✔
952
        if (!endswith(c, ".slice"))
121,371✔
953
                return false;
954

955
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
61,513✔
956
}
957

958
static const char* skip_slices(const char *p) {
43,538✔
959
        assert(p);
43,538✔
960

961
        /* Skips over all slice assignments */
962

963
        for (;;) {
133,068✔
964
                size_t n;
88,303✔
965

966
                p += strspn(p, "/");
88,303✔
967

968
                n = strcspn(p, "/");
88,303✔
969
                if (!valid_slice_name(p, n))
88,303✔
970
                        return p;
43,538✔
971

972
                p += n;
44,765✔
973
        }
974
}
975

976
int cg_path_get_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
17,758✔
977
        int r;
17,758✔
978

979
        assert(path);
17,758✔
980

981
        const char *e = skip_slices(path);
17,758✔
982

983
        _cleanup_free_ char *unit = NULL;
17,758✔
984
        r = cg_path_decode_unit(e, &unit);
17,758✔
985
        if (r < 0)
17,758✔
986
                return r;
987

988
        /* We skipped over the slices, don't accept any now */
989
        if (endswith(unit, ".slice"))
17,730✔
990
                return -ENXIO;
991

992
        if (ret_subgroup) {
17,730✔
993
                _cleanup_free_ char *subgroup = NULL;
×
994
                e += strcspn(e, "/");
595✔
995
                e += strspn(e, "/");
595✔
996

997
                if (isempty(e))
595✔
998
                        subgroup = NULL;
999
                else {
1000
                        subgroup = strdup(e);
234✔
1001
                        if (!subgroup)
234✔
1002
                                return -ENOMEM;
×
1003
                }
1004

1005
                path_simplify(subgroup);
595✔
1006

1007
                *ret_subgroup = TAKE_PTR(subgroup);
595✔
1008
        }
1009

1010
        if (ret_unit)
17,730✔
1011
                *ret_unit = TAKE_PTR(unit);
17,730✔
1012

1013
        return 0;
1014
}
1015

1016
int cg_path_get_unit_path(const char *path, char **ret) {
9,868✔
1017
        _cleanup_free_ char *path_copy = NULL;
9,868✔
1018
        char *unit_name;
9,868✔
1019

1020
        assert(path);
9,868✔
1021
        assert(ret);
9,868✔
1022

1023
        path_copy = strdup(path);
9,868✔
1024
        if (!path_copy)
9,868✔
1025
                return -ENOMEM;
1026

1027
        unit_name = (char*) skip_slices(path_copy);
9,868✔
1028
        unit_name[strcspn(unit_name, "/")] = 0;
9,868✔
1029

1030
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,868✔
1031
                return -ENXIO;
1032

1033
        *ret = TAKE_PTR(path_copy);
9,865✔
1034

1035
        return 0;
9,865✔
1036
}
1037

1038
int cg_pid_get_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
666✔
1039
        int r;
666✔
1040

1041
        _cleanup_free_ char *cgroup = NULL;
666✔
1042
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
666✔
1043
        if (r < 0)
666✔
1044
                return r;
1045

1046
        return cg_path_get_unit_full(cgroup, ret_unit, ret_subgroup);
666✔
1047
}
1048

1049
int cg_pidref_get_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
581✔
1050
        int r;
581✔
1051

1052
        if (!pidref_is_set(pidref))
581✔
1053
                return -ESRCH;
581✔
1054
        if (pidref_is_remote(pidref))
1,162✔
1055
                return -EREMOTE;
1056

1057
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
581✔
1058
        r = cg_pid_get_unit_full(pidref->pid, &unit, &subgroup);
581✔
1059
        if (r < 0)
581✔
1060
                return r;
1061

1062
        r = pidref_verify(pidref);
581✔
1063
        if (r < 0)
581✔
1064
                return r;
1065

1066
        if (ret_unit)
581✔
1067
                *ret_unit = TAKE_PTR(unit);
581✔
1068
        if (ret_subgroup)
581✔
1069
                *ret_subgroup = TAKE_PTR(subgroup);
22✔
1070
        return 0;
1071
}
1072

1073
static const char* skip_session(const char *p) {
15,508✔
1074
        size_t n;
15,508✔
1075

1076
        /* Skip session-*.scope, but require it to be there. */
1077

1078
        if (isempty(p))
15,508✔
1079
                return NULL;
1080

1081
        p += strspn(p, "/");
15,504✔
1082

1083
        n = strcspn(p, "/");
15,504✔
1084
        if (n < STRLEN("session-x.scope"))
15,504✔
1085
                return NULL;
1086

1087
        const char *s = startswith(p, "session-");
15,338✔
1088
        if (!s)
15,338✔
1089
                return NULL;
1090

1091
        /* Note that session scopes never need unescaping, since they cannot conflict with the kernel's
1092
         * own names, hence we don't need to call cg_unescape() here. */
1093
        char *f = strndupa_safe(s, p + n - s),
27✔
1094
             *e = endswith(f, ".scope");
27✔
1095
        if (!e)
27✔
1096
                return NULL;
1097
        *e = '\0';
27✔
1098

1099
        if (!session_id_valid(f))
27✔
1100
                return NULL;
1101

1102
        return skip_leading_slash(p + n);
27✔
1103
}
1104

1105
static const char* skip_user_manager(const char *p) {
15,912✔
1106
        size_t n;
15,912✔
1107

1108
        /* Skip user@*.service or capsule@*.service, but require either of them to be there. */
1109

1110
        if (isempty(p))
15,912✔
1111
                return NULL;
15,912✔
1112

1113
        p += strspn(p, "/");
15,908✔
1114

1115
        n = strcspn(p, "/");
15,908✔
1116
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,908✔
1117
                return NULL;
1118

1119
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
1120
         * resolve user/capsule name. */
1121
        _cleanup_free_ char *unit_name = strndup(p, n);
15,744✔
1122
        if (!unit_name)
15,744✔
1123
                return NULL;
1124

1125
        _cleanup_free_ char *i = NULL;
15,744✔
1126
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
15,744✔
1127

1128
        if (type != UNIT_NAME_INSTANCE)
15,744✔
1129
                return NULL;
1130

1131
        /* Note that user manager services never need unescaping, since they cannot conflict with the
1132
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
1133
         * instance names, they should be always valid as we validate them upon unit start. */
1134
        if (!(startswith(unit_name, "user@") && parse_uid(i, NULL) >= 0) &&
633✔
1135
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
122✔
1136
                return NULL;
112✔
1137

1138
        return skip_leading_slash(p + n);
404✔
1139
}
1140

1141
static const char* skip_user_prefix(const char *path) {
15,912✔
1142
        const char *e, *t;
15,912✔
1143

1144
        assert(path);
15,912✔
1145

1146
        /* Skip slices, if there are any */
1147
        e = skip_slices(path);
15,912✔
1148

1149
        /* Skip the user manager, if it's in the path now... */
1150
        t = skip_user_manager(e);
15,912✔
1151
        if (t)
15,912✔
1152
                return t;
1153

1154
        /* Alternatively skip the user session if it is in the path... */
1155
        return skip_session(e);
15,508✔
1156
}
1157

1158
int cg_path_get_user_unit(const char *path, char **ret) {
7,985✔
1159
        const char *t;
7,985✔
1160

1161
        assert(path);
7,985✔
1162

1163
        t = skip_user_prefix(path);
7,985✔
1164
        if (!t)
7,985✔
1165
                return -ENXIO;
1166

1167
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
1168
         * parser. */
1169
        return cg_path_get_unit(t, ret);
224✔
1170
}
1171

1172
int cg_pid_get_user_unit(pid_t pid, char **ret_unit) {
59✔
1173
        _cleanup_free_ char *cgroup = NULL;
59✔
1174
        int r;
59✔
1175

1176
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
59✔
1177
        if (r < 0)
59✔
1178
                return r;
1179

1180
        return cg_path_get_user_unit(cgroup, ret_unit);
59✔
1181
}
1182

1183
int cg_path_get_machine_name(const char *path, char **ret_machine) {
41✔
1184
        _cleanup_free_ char *u = NULL;
41✔
1185
        const char *sl;
41✔
1186
        int r;
41✔
1187

1188
        r = cg_path_get_unit(path, &u);
41✔
1189
        if (r < 0)
41✔
1190
                return r;
1191

1192
        sl = strjoina("/run/systemd/machines/unit:", u);
205✔
1193
        return readlink_malloc(sl, ret_machine);
41✔
1194
}
1195

1196
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
41✔
1197
        _cleanup_free_ char *cgroup = NULL;
41✔
1198
        int r;
41✔
1199

1200
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
41✔
1201
        if (r < 0)
41✔
1202
                return r;
1203

1204
        return cg_path_get_machine_name(cgroup, ret_machine);
41✔
1205
}
1206

1207
int cg_path_get_session(const char *path, char **ret_session) {
8,833✔
1208
        _cleanup_free_ char *unit = NULL;
8,833✔
1209
        char *start, *end;
8,833✔
1210
        int r;
8,833✔
1211

1212
        assert(path);
8,833✔
1213

1214
        r = cg_path_get_unit(path, &unit);
8,833✔
1215
        if (r < 0)
8,833✔
1216
                return r;
1217

1218
        start = startswith(unit, "session-");
8,832✔
1219
        if (!start)
8,832✔
1220
                return -ENXIO;
1221
        end = endswith(start, ".scope");
351✔
1222
        if (!end)
351✔
1223
                return -ENXIO;
1224

1225
        *end = 0;
351✔
1226
        if (!session_id_valid(start))
351✔
1227
                return -ENXIO;
1228

1229
        if (!ret_session)
350✔
1230
                return 0;
1231

1232
        return strdup_to(ret_session, start);
350✔
1233
}
1234

1235
int cg_pid_get_session(pid_t pid, char **ret_session) {
847✔
1236
        _cleanup_free_ char *cgroup = NULL;
847✔
1237
        int r;
847✔
1238

1239
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
847✔
1240
        if (r < 0)
847✔
1241
                return r;
1242

1243
        return cg_path_get_session(cgroup, ret_session);
847✔
1244
}
1245

1246
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
362✔
1247
        int r;
362✔
1248

1249
        if (!pidref_is_set(pidref))
362✔
1250
                return -ESRCH;
362✔
1251
        if (pidref_is_remote(pidref))
724✔
1252
                return -EREMOTE;
1253

1254
        _cleanup_free_ char *session = NULL;
362✔
1255
        r = cg_pid_get_session(pidref->pid, &session);
362✔
1256
        if (r < 0)
362✔
1257
                return r;
1258

1259
        r = pidref_verify(pidref);
306✔
1260
        if (r < 0)
306✔
1261
                return r;
1262

1263
        if (ret)
306✔
1264
                *ret = TAKE_PTR(session);
306✔
1265
        return 0;
1266
}
1267

1268
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
8,193✔
1269
        _cleanup_free_ char *slice = NULL;
8,193✔
1270
        char *start, *end;
8,193✔
1271
        int r;
8,193✔
1272

1273
        assert(path);
8,193✔
1274

1275
        r = cg_path_get_slice(path, &slice);
8,193✔
1276
        if (r < 0)
8,193✔
1277
                return r;
1278

1279
        start = startswith(slice, "user-");
8,193✔
1280
        if (!start)
8,193✔
1281
                return -ENXIO;
1282

1283
        end = endswith(start, ".slice");
321✔
1284
        if (!end)
321✔
1285
                return -ENXIO;
1286

1287
        *end = 0;
321✔
1288
        if (parse_uid(start, ret_uid) < 0)
321✔
1289
                return -ENXIO;
×
1290

1291
        return 0;
1292
}
1293

1294
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
229✔
1295
        _cleanup_free_ char *cgroup = NULL;
229✔
1296
        int r;
229✔
1297

1298
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
229✔
1299
        if (r < 0)
229✔
1300
                return r;
1301

1302
        return cg_path_get_owner_uid(cgroup, ret_uid);
229✔
1303
}
1304

1305
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
56✔
1306
        int r;
56✔
1307

1308
        if (!pidref_is_set(pidref))
56✔
1309
                return -ESRCH;
56✔
1310
        if (pidref_is_remote(pidref))
56✔
1311
                return -EREMOTE;
1312

1313
        uid_t uid;
56✔
1314
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
56✔
1315
        if (r < 0)
56✔
1316
                return r;
1317

1318
        r = pidref_verify(pidref);
13✔
1319
        if (r < 0)
13✔
1320
                return r;
1321

1322
        if (ret)
13✔
1323
                *ret = uid;
13✔
1324

1325
        return 0;
1326
}
1327

1328
int cg_path_get_slice(const char *p, char **ret_slice) {
16,383✔
1329
        const char *e = NULL;
16,383✔
1330

1331
        assert(p);
16,383✔
1332

1333
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1334
         * the first non-slice unit. */
1335

1336
        for (;;) {
49,879✔
1337
                const char *s;
33,131✔
1338
                int n;
33,131✔
1339

1340
                n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s);
33,131✔
1341
                if (n < 0)
33,131✔
1342
                        return n;
×
1343
                if (!valid_slice_name(s, n))
33,131✔
1344
                        break;
1345

1346
                e = s;
16,748✔
1347
        }
1348

1349
        if (e)
16,383✔
1350
                return cg_path_decode_unit(e, ret_slice);
16,138✔
1351

1352
        if (ret_slice)
245✔
1353
                return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
245✔
1354

1355
        return 0;
1356
}
1357

1358
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
63✔
1359
        _cleanup_free_ char *cgroup = NULL;
63✔
1360
        int r;
63✔
1361

1362
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
63✔
1363
        if (r < 0)
63✔
1364
                return r;
1365

1366
        return cg_path_get_slice(cgroup, ret_slice);
63✔
1367
}
1368

1369
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,927✔
1370
        const char *t;
7,927✔
1371
        assert(p);
7,927✔
1372

1373
        t = skip_user_prefix(p);
7,927✔
1374
        if (!t)
7,927✔
1375
                return -ENXIO;
1376

1377
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1378
         * from here on. */
1379
        return cg_path_get_slice(t, ret_slice);
207✔
1380
}
1381

1382
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1383
        _cleanup_free_ char *cgroup = NULL;
1✔
1384
        int r;
1✔
1385

1386
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1✔
1387
        if (r < 0)
1✔
1388
                return r;
1389

1390
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1391
}
1392

1393
bool cg_needs_escape(const char *p) {
21,355✔
1394

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

1400
        if (!filename_is_valid(p))
21,355✔
1401
                return true;
1402

1403
        if (IN_SET(p[0], '_', '.'))
21,351✔
1404
                return true;
1405

1406
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
21,345✔
1407
                return true;
2✔
1408

1409
        if (startswith(p, "cgroup."))
21,343✔
1410
                return true;
1411

1412
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
298,774✔
1413
                const char *q;
277,433✔
1414

1415
                q = startswith(p, cgroup_controller_to_string(c));
277,433✔
1416
                if (!q)
277,433✔
1417
                        continue;
277,433✔
1418

1419
                if (q[0] == '.')
×
1420
                        return true;
1421
        }
1422

1423
        return false;
1424
}
1425

1426
int cg_escape(const char *p, char **ret) {
21,131✔
1427
        _cleanup_free_ char *n = NULL;
21,131✔
1428

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

1434
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1435

1436
        if (cg_needs_escape(p)) {
21,131✔
1437
                n = strjoin("_", p);
7✔
1438
                if (!n)
7✔
1439
                        return -ENOMEM;
1440

1441
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1442
                        return -EINVAL;
1443
        } else {
1444
                n = strdup(p);
21,124✔
1445
                if (!n)
21,124✔
1446
                        return -ENOMEM;
1447
        }
1448

1449
        *ret = TAKE_PTR(n);
21,131✔
1450
        return 0;
21,131✔
1451
}
1452

1453
char* cg_unescape(const char *p) {
105,471✔
1454
        assert(p);
105,471✔
1455

1456
        /* The return value of this function (unlike cg_escape())
1457
         * doesn't need free()! */
1458

1459
        if (p[0] == '_')
105,471✔
1460
                return (char*) p+1;
14✔
1461

1462
        return (char*) p;
1463
}
1464

1465
#define CONTROLLER_VALID                        \
1466
        DIGITS LETTERS                          \
1467
        "_"
1468

1469
bool cg_controller_is_valid(const char *p) {
283,144✔
1470
        const char *t, *s;
283,144✔
1471

1472
        if (!p)
283,144✔
1473
                return false;
1474

1475
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
283,144✔
1476
                return true;
1477

1478
        s = startswith(p, "name=");
49,485✔
1479
        if (s)
49,485✔
1480
                p = s;
2✔
1481

1482
        if (IN_SET(*p, 0, '_'))
49,485✔
1483
                return false;
1484

1485
        for (t = p; *t; t++)
318,099✔
1486
                if (!strchr(CONTROLLER_VALID, *t))
268,625✔
1487
                        return false;
1488

1489
        if (t - p > NAME_MAX)
49,474✔
1490
                return false;
×
1491

1492
        return true;
1493
}
1494

1495
int cg_slice_to_path(const char *unit, char **ret) {
9,107✔
1496
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
9,107✔
1497
        const char *dash;
9,107✔
1498
        int r;
9,107✔
1499

1500
        assert(unit);
9,107✔
1501
        assert(ret);
9,107✔
1502

1503
        if (streq(unit, SPECIAL_ROOT_SLICE))
9,107✔
1504
                return strdup_to(ret, "");
7✔
1505

1506
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
9,100✔
1507
                return -EINVAL;
1508

1509
        if (!endswith(unit, ".slice"))
9,089✔
1510
                return -EINVAL;
1511

1512
        r = unit_name_to_prefix(unit, &p);
9,088✔
1513
        if (r < 0)
9,088✔
1514
                return r;
1515

1516
        dash = strchr(p, '-');
9,088✔
1517

1518
        /* Don't allow initial dashes */
1519
        if (dash == p)
9,088✔
1520
                return -EINVAL;
1521

1522
        while (dash) {
9,391✔
1523
                _cleanup_free_ char *escaped = NULL;
308✔
1524
                char n[dash - p + sizeof(".slice")];
308✔
1525

1526
#if HAS_FEATURE_MEMORY_SANITIZER
1527
                /* msan doesn't instrument stpncpy, so it thinks
1528
                 * n is later used uninitialized:
1529
                 * https://github.com/google/sanitizers/issues/926
1530
                 */
1531
                zero(n);
1532
#endif
1533

1534
                /* Don't allow trailing or double dashes */
1535
                if (IN_SET(dash[1], 0, '-'))
308✔
1536
                        return -EINVAL;
1537

1538
                strcpy(stpncpy(n, p, dash - p), ".slice");
306✔
1539
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
306✔
1540
                        return -EINVAL;
1541

1542
                r = cg_escape(n, &escaped);
306✔
1543
                if (r < 0)
306✔
1544
                        return r;
1545

1546
                if (!strextend(&s, escaped, "/"))
306✔
1547
                        return -ENOMEM;
1548

1549
                dash = strchr(dash+1, '-');
306✔
1550
        }
1551

1552
        r = cg_escape(unit, &e);
9,083✔
1553
        if (r < 0)
9,083✔
1554
                return r;
1555

1556
        if (!strextend(&s, e))
9,083✔
1557
                return -ENOMEM;
1558

1559
        *ret = TAKE_PTR(s);
9,083✔
1560
        return 0;
9,083✔
1561
}
1562

1563
int cg_is_threaded(const char *path) {
×
1564
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
1565
        _cleanup_strv_free_ char **v = NULL;
×
1566
        int r;
×
1567

1568
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.type", &fs);
×
1569
        if (r < 0)
×
1570
                return r;
1571

1572
        r = read_full_virtual_file(fs, &contents, NULL);
×
1573
        if (r == -ENOENT)
×
1574
                return false; /* Assume no. */
1575
        if (r < 0)
×
1576
                return r;
1577

1578
        v = strv_split(contents, NULL);
×
1579
        if (!v)
×
1580
                return -ENOMEM;
1581

1582
        /* If the cgroup is in the threaded mode, it contains "threaded".
1583
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
1584
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1585
}
1586

1587
int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) {
40,398✔
1588
        _cleanup_free_ char *p = NULL;
40,398✔
1589
        int r;
40,398✔
1590

1591
        assert(attribute);
40,398✔
1592

1593
        r = cg_get_path(controller, path, attribute, &p);
40,398✔
1594
        if (r < 0)
40,398✔
1595
                return r;
1596

1597
        /* https://lore.kernel.org/all/20250419183545.1982187-1-shakeel.butt@linux.dev/ adds O_NONBLOCK
1598
         * semantics to memory.max and memory.high to skip synchronous memory reclaim when O_NONBLOCK is
1599
         * enabled. Let's always open cgroupv2 attribute files in nonblocking mode to immediately take
1600
         * advantage of this and any other asynchronous resource reclaim that's added to the cgroupv2 API in
1601
         * the future. */
1602
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER|WRITE_STRING_FILE_OPEN_NONBLOCKING);
40,398✔
1603
}
1604

1605
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) {
26,334✔
1606
        _cleanup_free_ char *p = NULL;
26,334✔
1607
        int r;
26,334✔
1608

1609
        assert(attribute);
26,334✔
1610

1611
        r = cg_get_path(controller, path, attribute, &p);
26,334✔
1612
        if (r < 0)
26,334✔
1613
                return r;
1614

1615
        return read_one_line_file(p, ret);
26,334✔
1616
}
1617

1618
int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret) {
22,291✔
1619
        _cleanup_free_ char *value = NULL;
22,291✔
1620
        uint64_t v;
22,291✔
1621
        int r;
22,291✔
1622

1623
        assert(ret);
22,291✔
1624

1625
        r = cg_get_attribute(controller, path, attribute, &value);
22,291✔
1626
        if (r == -ENOENT)
22,291✔
1627
                return -ENODATA;
1628
        if (r < 0)
20,420✔
1629
                return r;
1630

1631
        if (streq(value, "max")) {
20,420✔
1632
                *ret = CGROUP_LIMIT_MAX;
4,882✔
1633
                return 0;
4,882✔
1634
        }
1635

1636
        r = safe_atou64(value, &v);
15,538✔
1637
        if (r < 0)
15,538✔
1638
                return r;
1639

1640
        *ret = v;
15,538✔
1641
        return 0;
15,538✔
1642
}
1643

1644
int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute) {
64✔
1645
        _cleanup_free_ char *value = NULL;
64✔
1646
        int r;
64✔
1647

1648
        r = cg_get_attribute(controller, path, attribute, &value);
64✔
1649
        if (r == -ENOENT)
64✔
1650
                return -ENODATA;
1651
        if (r < 0)
64✔
1652
                return r;
1653

1654
        return parse_boolean(value);
64✔
1655
}
1656

1657
int cg_get_owner(const char *path, uid_t *ret_uid) {
35✔
1658
        _cleanup_free_ char *f = NULL;
35✔
1659
        struct stat stats;
35✔
1660
        int r;
35✔
1661

1662
        assert(ret_uid);
35✔
1663

1664
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &f);
35✔
1665
        if (r < 0)
35✔
1666
                return r;
1667

1668
        if (stat(f, &stats) < 0)
35✔
1669
                return -errno;
16✔
1670

1671
        r = stat_verify_directory(&stats);
19✔
1672
        if (r < 0)
19✔
1673
                return r;
1674

1675
        *ret_uid = stats.st_uid;
19✔
1676
        return 0;
19✔
1677
}
1678

1679
int cg_get_keyed_attribute(
20,862✔
1680
                const char *controller,
1681
                const char *path,
1682
                const char *attribute,
1683
                char * const *keys,
1684
                char **values) {
1685

1686
        _cleanup_free_ char *filename = NULL, *contents = NULL;
20,862✔
1687
        size_t n;
20,862✔
1688
        int r;
20,862✔
1689

1690
        assert(path);
20,862✔
1691
        assert(attribute);
20,862✔
1692

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

1699
        r = cg_get_path(controller, path, attribute, &filename);
20,862✔
1700
        if (r < 0)
20,862✔
1701
                return r;
1702

1703
        r = read_full_file(filename, &contents, /* ret_size = */ NULL);
20,862✔
1704
        if (r < 0)
20,862✔
1705
                return r;
1706

1707
        n = strv_length(keys);
18,707✔
1708
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
18,707✔
1709
                return 0;
1710
        assert(strv_is_uniq(keys));
18,707✔
1711

1712
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1713
        char **v = newa0(char*, n);
18,707✔
1714
        size_t n_done = 0;
18,707✔
1715

1716
        for (const char *p = contents; *p;) {
62,711✔
1717
                const char *w;
1718
                size_t i;
1719

1720
                for (i = 0; i < n; i++) {
106,715✔
1721
                        w = first_word(p, keys[i]);
69,138✔
1722
                        if (w)
69,138✔
1723
                                break;
1724
                }
1725

1726
                if (w) {
62,711✔
1727
                        if (v[i]) { /* duplicate entry? */
25,134✔
1728
                                r = -EBADMSG;
×
1729
                                goto fail;
×
1730
                        }
1731

1732
                        size_t l = strcspn(w, NEWLINE);
25,134✔
1733

1734
                        v[i] = strndup(w, l);
25,134✔
1735
                        if (!v[i]) {
25,134✔
1736
                                r = -ENOMEM;
×
1737
                                goto fail;
×
1738
                        }
1739

1740
                        n_done++;
25,134✔
1741
                        if (n_done >= n)
25,134✔
1742
                                break;
1743

1744
                        p = w + l;
6,427✔
1745
                } else
1746
                        p += strcspn(p, NEWLINE);
37,577✔
1747

1748
                p += strspn(p, NEWLINE);
44,004✔
1749
        }
1750

1751
        if (n_done < n) {
18,707✔
1752
                r = -ENXIO;
×
1753
                goto fail;
×
1754
        }
1755

1756
        memcpy(values, v, sizeof(char*) * n);
18,707✔
1757
        return 0;
18,707✔
1758

1759
fail:
×
1760
        free_many_charp(v, n);
20,862✔
1761
        return r;
1762
}
1763

1764
int cg_mask_to_string(CGroupMask mask, char **ret) {
8,232✔
1765
        _cleanup_free_ char *s = NULL;
8,232✔
1766
        bool space = false;
8,232✔
1767
        CGroupController c;
8,232✔
1768
        size_t n = 0;
8,232✔
1769

1770
        assert(ret);
8,232✔
1771

1772
        if (mask == 0) {
8,232✔
1773
                *ret = NULL;
4,655✔
1774
                return 0;
4,655✔
1775
        }
1776

1777
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
50,078✔
1778
                const char *k;
46,501✔
1779
                size_t l;
46,501✔
1780

1781
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
46,501✔
1782
                        continue;
26,158✔
1783

1784
                k = cgroup_controller_to_string(c);
20,343✔
1785
                l = strlen(k);
20,343✔
1786

1787
                if (!GREEDY_REALLOC(s, n + space + l + 1))
20,343✔
1788
                        return -ENOMEM;
1789

1790
                if (space)
20,343✔
1791
                        s[n] = ' ';
16,766✔
1792
                memcpy(s + n + space, k, l);
20,343✔
1793
                n += space + l;
20,343✔
1794

1795
                space = true;
20,343✔
1796
        }
1797

1798
        assert(s);
3,577✔
1799

1800
        s[n] = 0;
3,577✔
1801
        *ret = TAKE_PTR(s);
3,577✔
1802

1803
        return 0;
3,577✔
1804
}
1805

1806
int cg_mask_from_string(const char *value, CGroupMask *ret) {
3,649✔
1807
        CGroupMask m = 0;
3,649✔
1808

1809
        assert(ret);
3,649✔
1810
        assert(value);
3,649✔
1811

1812
        for (;;) {
24,682✔
1813
                _cleanup_free_ char *n = NULL;
21,033✔
1814
                CGroupController v;
24,682✔
1815
                int r;
24,682✔
1816

1817
                r = extract_first_word(&value, &n, NULL, 0);
24,682✔
1818
                if (r < 0)
24,682✔
1819
                        return r;
×
1820
                if (r == 0)
24,682✔
1821
                        break;
1822

1823
                v = cgroup_controller_from_string(n);
21,033✔
1824
                if (v < 0)
21,033✔
1825
                        continue;
722✔
1826

1827
                m |= CGROUP_CONTROLLER_TO_MASK(v);
20,311✔
1828
        }
1829

1830
        *ret = m;
3,649✔
1831
        return 0;
3,649✔
1832
}
1833

1834
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
486✔
1835
        CGroupMask mask;
486✔
1836
        int r;
486✔
1837

1838
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1839
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1840
         * pseudo-controllers. */
1841

1842
        r = cg_all_unified();
486✔
1843
        if (r < 0)
486✔
1844
                return r;
486✔
1845
        if (r > 0) {
486✔
1846
                _cleanup_free_ char *controllers = NULL, *path = NULL;
486✔
1847

1848
                /* In the unified hierarchy we can read the supported and accessible controllers from
1849
                 * the top-level cgroup attribute */
1850

1851
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path);
486✔
1852
                if (r < 0)
486✔
1853
                        return r;
1854

1855
                r = read_one_line_file(path, &controllers);
486✔
1856
                if (r < 0)
486✔
1857
                        return r;
1858

1859
                r = cg_mask_from_string(controllers, &mask);
486✔
1860
                if (r < 0)
486✔
1861
                        return r;
1862

1863
                /* Mask controllers that are not supported in unified hierarchy. */
1864
                mask &= CGROUP_MASK_V2;
486✔
1865

1866
        } else {
1867
                CGroupController c;
×
1868

1869
                /* In the legacy hierarchy, we check which hierarchies are accessible. */
1870

1871
                mask = 0;
×
1872
                for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
×
1873
                        CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
×
1874
                        const char *n;
×
1875

1876
                        if (!FLAGS_SET(CGROUP_MASK_V1, bit))
×
1877
                                continue;
×
1878

1879
                        n = cgroup_controller_to_string(c);
×
1880
                        if (controller_is_v1_accessible(root, n) >= 0)
×
1881
                                mask |= bit;
×
1882
                }
1883
        }
1884

1885
        *ret = mask;
486✔
1886
        return 0;
486✔
1887
}
1888

1889
int cg_mask_supported(CGroupMask *ret) {
239✔
1890
        _cleanup_free_ char *root = NULL;
239✔
1891
        int r;
239✔
1892

1893
        r = cg_get_root_path(&root);
239✔
1894
        if (r < 0)
239✔
1895
                return r;
1896

1897
        return cg_mask_supported_subtree(root, ret);
239✔
1898
}
1899

1900
/* The hybrid mode was initially implemented in v232 and simply mounted cgroup2 on
1901
 * /sys/fs/cgroup/systemd. This unfortunately broke other tools (such as docker) which expected the v1
1902
 * "name=systemd" hierarchy on /sys/fs/cgroup/systemd. From v233 and on, the hybrid mode mounts v2 on
1903
 * /sys/fs/cgroup/unified and maintains "name=systemd" hierarchy on /sys/fs/cgroup/systemd for compatibility
1904
 * with other tools.
1905
 *
1906
 * To keep live upgrade working, we detect and support v232 layout. When v232 layout is detected, to keep
1907
 * cgroup v2 process management but disable the compat dual layout, we return true on
1908
 * cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) and false on cg_hybrid_unified().
1909
 */
1910
static thread_local bool unified_systemd_v232;
1911

1912
int cg_unified_cached(bool flush) {
288,434✔
1913
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
288,434✔
1914

1915
        struct statfs fs;
288,434✔
1916

1917
        /* Checks if we support the unified hierarchy. Returns an
1918
         * error when the cgroup hierarchies aren't mounted yet or we
1919
         * have any other trouble determining if the unified hierarchy
1920
         * is supported. */
1921

1922
        if (flush)
288,434✔
1923
                unified_cache = CGROUP_UNIFIED_UNKNOWN;
4✔
1924
        else if (unified_cache >= CGROUP_UNIFIED_NONE)
288,430✔
1925
                return unified_cache;
288,434✔
1926

1927
        if (statfs("/sys/fs/cgroup/", &fs) < 0)
13,057✔
1928
                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/\") failed: %m");
×
1929

1930
        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
13,057✔
1931
                log_debug("Found cgroup2 on /sys/fs/cgroup/, full unified hierarchy");
13,057✔
1932
                unified_cache = CGROUP_UNIFIED_ALL;
13,057✔
1933
        } else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) {
×
1934
                if (statfs("/sys/fs/cgroup/unified/", &fs) == 0 &&
×
1935
                    F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
×
1936
                        log_debug("Found cgroup2 on /sys/fs/cgroup/unified, unified hierarchy for systemd controller");
×
1937
                        unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
1938
                        unified_systemd_v232 = false;
×
1939
                } else {
1940
                        if (statfs("/sys/fs/cgroup/systemd/", &fs) < 0) {
×
1941
                                if (errno == ENOENT) {
×
1942
                                        /* Some other software may have set up /sys/fs/cgroup in a configuration we do not recognize. */
1943
                                        log_debug_errno(errno, "Unsupported cgroupsv1 setup detected: name=systemd hierarchy not found.");
×
1944
                                        return -ENOMEDIUM;
×
1945
                                }
1946
                                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/systemd\" failed: %m");
×
1947
                        }
1948

1949
                        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
×
1950
                                log_debug("Found cgroup2 on /sys/fs/cgroup/systemd, unified hierarchy for systemd controller (v232 variant)");
×
1951
                                unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
1952
                                unified_systemd_v232 = true;
×
1953
                        } else if (F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) {
×
1954
                                log_debug("Found cgroup on /sys/fs/cgroup/systemd, legacy hierarchy");
×
1955
                                unified_cache = CGROUP_UNIFIED_NONE;
×
1956
                        } else {
1957
                                log_debug("Unexpected filesystem type %llx mounted on /sys/fs/cgroup/systemd, assuming legacy hierarchy",
×
1958
                                          (unsigned long long) fs.f_type);
1959
                                unified_cache = CGROUP_UNIFIED_NONE;
×
1960
                        }
1961
                }
1962
        } else if (F_TYPE_EQUAL(fs.f_type, SYSFS_MAGIC)) {
×
1963
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
1964
                                       "No filesystem is currently mounted on /sys/fs/cgroup.");
1965
        } else
1966
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
1967
                                       "Unknown filesystem type %llx mounted on /sys/fs/cgroup.",
1968
                                       (unsigned long long)fs.f_type);
1969

1970
        return unified_cache;
13,057✔
1971
}
1972

1973
int cg_unified_controller(const char *controller) {
40,229✔
1974
        int r;
40,229✔
1975

1976
        r = cg_unified_cached(false);
40,229✔
1977
        if (r < 0)
40,229✔
1978
                return r;
1979

1980
        if (r == CGROUP_UNIFIED_NONE)
40,229✔
1981
                return false;
1982

1983
        if (r >= CGROUP_UNIFIED_ALL)
40,229✔
1984
                return true;
1985

1986
        return streq_ptr(controller, SYSTEMD_CGROUP_CONTROLLER);
×
1987
}
1988

1989
int cg_all_unified(void) {
248,199✔
1990
        int r;
248,199✔
1991

1992
        r = cg_unified_cached(false);
248,199✔
1993
        if (r < 0)
248,199✔
1994
                return r;
1995

1996
        return r >= CGROUP_UNIFIED_ALL;
248,199✔
1997
}
1998

1999
int cg_hybrid_unified(void) {
1✔
2000
        int r;
1✔
2001

2002
        r = cg_unified_cached(false);
1✔
2003
        if (r < 0)
1✔
2004
                return r;
2005

2006
        return r == CGROUP_UNIFIED_SYSTEMD && !unified_systemd_v232;
1✔
2007
}
2008

2009
int cg_is_delegated(const char *path) {
19✔
2010
        int r;
19✔
2011

2012
        assert(path);
19✔
2013

2014
        r = cg_get_xattr_bool(path, "trusted.delegate");
19✔
2015
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
19✔
2016
                return r;
2017

2018
        /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the assumption
2019
         * that whoever is trusted enough to own the cgroup, is also trusted enough to decide if it is
2020
         * delegated or not this should be safe. */
2021
        r = cg_get_xattr_bool(path, "user.delegate");
6✔
2022
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
6✔
2023
}
2024

2025
int cg_is_delegated_fd(int fd) {
199✔
2026
        int r;
199✔
2027

2028
        assert(fd >= 0);
199✔
2029

2030
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
199✔
2031
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
199✔
2032
                return r;
2033

2034
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
185✔
2035
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
185✔
2036
}
2037

2038
int cg_has_coredump_receive(const char *path) {
2✔
2039
        int r;
2✔
2040

2041
        assert(path);
2✔
2042

2043
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
2044
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
2045
                return false;
×
2046

2047
        return r;
2048
}
2049

2050
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2051
        [CGROUP_IO_RBPS_MAX]  = CGROUP_LIMIT_MAX,
2052
        [CGROUP_IO_WBPS_MAX]  = CGROUP_LIMIT_MAX,
2053
        [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX,
2054
        [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX,
2055
};
2056

2057
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2058
        [CGROUP_IO_RBPS_MAX]  = "IOReadBandwidthMax",
2059
        [CGROUP_IO_WBPS_MAX]  = "IOWriteBandwidthMax",
2060
        [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax",
2061
        [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax",
2062
};
2063

2064
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
6,390✔
2065

2066
void cgroup_io_limits_list(void) {
20✔
2067
        DUMP_STRING_TABLE(cgroup_io_limit_type, CGroupIOLimitType, _CGROUP_IO_LIMIT_TYPE_MAX);
100✔
2068
}
20✔
2069

2070
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
2071
        [CGROUP_CONTROLLER_CPU]                             = "cpu",
2072
        [CGROUP_CONTROLLER_CPUACCT]                         = "cpuacct",
2073
        [CGROUP_CONTROLLER_CPUSET]                          = "cpuset",
2074
        [CGROUP_CONTROLLER_IO]                              = "io",
2075
        [CGROUP_CONTROLLER_BLKIO]                           = "blkio",
2076
        [CGROUP_CONTROLLER_MEMORY]                          = "memory",
2077
        [CGROUP_CONTROLLER_DEVICES]                         = "devices",
2078
        [CGROUP_CONTROLLER_PIDS]                            = "pids",
2079
        [CGROUP_CONTROLLER_BPF_FIREWALL]                    = "bpf-firewall",
2080
        [CGROUP_CONTROLLER_BPF_DEVICES]                     = "bpf-devices",
2081
        [CGROUP_CONTROLLER_BPF_FOREIGN]                     = "bpf-foreign",
2082
        [CGROUP_CONTROLLER_BPF_SOCKET_BIND]                 = "bpf-socket-bind",
2083
        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
2084
};
2085

2086
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
343,053✔
2087

2088
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
2089
        [MANAGED_OOM_AUTO] = "auto",
2090
        [MANAGED_OOM_KILL] = "kill",
2091
};
2092

2093
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
31,331✔
2094

2095
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
2096
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
2097
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
2098
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
2099
};
2100

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