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

systemd / systemd / 16764148981

05 Aug 2025 11:21PM UTC coverage: 72.223% (+0.02%) from 72.2%
16764148981

push

github

bluca
test: ensure printf builtin buffers and does a single write when piping into socat

Should hopefully solve this:

[  111.084463] systemd[1]: testservice-50k.service: Got notification message from PID 2651: RELOADING=1
[  111.085980] systemd[1]: testservice-50k.service: Got notification message from PID 2651: MONOTONIC_USEC=111072508

ie, pid1 receives RELOADING=1 and MONOTONIC_USEC= in different messages instead
of a single one, and reload-notify breaks

Follow-up for 3998b30a0

Fixes https://github.com/systemd/systemd/issues/37626

302398 of 418703 relevant lines covered (72.22%)

641802.62 hits per line

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

85.14
/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,661✔
101
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
4,661✔
102
        int mnt_id;
4,661✔
103

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

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

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

123
        assert(ret);
14,040✔
124

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

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

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

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

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

142
        assert(f);
9,041✔
143
        assert(ret);
9,041✔
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,041✔
149
                errno = 0;
9,041✔
150
                if (fscanf(f, "%lu", &ul) != 1) {
9,041✔
151

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

157
                        return errno_or_else(EIO);
×
158
                }
159

160
                if (ul > PID_T_MAX)
3,756✔
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,756✔
167
                        continue;
×
168

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

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

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

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

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

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

194
                r = pidref_set_pid(ret, pid);
1,588✔
195
                if (r >= 0)
1,588✔
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

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

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

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

213
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
1✔
214
                return (supported = true);
1✔
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,618✔
221
        _cleanup_free_ char *fs = NULL;
13,618✔
222
        DIR *d;
13,618✔
223
        int r;
13,618✔
224

225
        assert(ret);
13,618✔
226

227
        /* This is not recursive! */
228

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

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

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

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

245
        FOREACH_DIRENT_ALL(de, d, return -errno) {
223,868✔
246
                if (de->d_type != DT_DIR)
219,005✔
247
                        continue;
208,129✔
248

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

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

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

259
int cg_kill(
13,551✔
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,551✔
268
        int r, ret = 0;
13,551✔
269

270
        assert(path);
13,551✔
271
        assert(sig >= 0);
13,551✔
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,551✔
276
                flags &= ~CGROUP_SIGCONT;
1,975✔
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,551✔
284
                killed_pids = allocated_set = set_new(NULL);
680✔
285
                if (!killed_pids)
680✔
286
                        return -ENOMEM;
287
        }
288

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

294
                done = true;
13,628✔
295

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

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

305
                        r = cg_read_pidref(f, &pidref, flags);
6,156✔
306
                        if (r == -ENODEV) {
6,156✔
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,156✔
314
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
315
                        if (r == 0)
6,156✔
316
                                break;
317

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

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

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

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

333
                        /* If we haven't killed this process yet, kill it */
334
                        r = pidref_kill(&pidref, sig);
219✔
335
                        if (r < 0 && r != -ESRCH)
219✔
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) {
219✔
338
                                if (flags & CGROUP_SIGCONT)
219✔
339
                                        (void) pidref_kill(&pidref, SIGCONT);
124✔
340

341
                                if (ret == 0) {
219✔
342
                                        if (log_kill)
147✔
343
                                                ret = ret_log_kill;
344
                                        else
345
                                                ret = 1;
53✔
346
                                }
347
                        }
348

349
                        done = false;
219✔
350

351
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
219✔
352
                        if (r < 0)
219✔
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,658✔
360

361
        return ret;
362
}
363

364
int cg_kill_recursive(
12,869✔
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,869✔
374
        int r, ret;
12,869✔
375

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

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

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

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

392
                return ret;
8,970✔
393
        }
394

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

398
                r = cg_read_subgroup(d, &fn);
4,008✔
399
                if (r < 0) {
4,008✔
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,008✔
404
                        break;
405

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

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

417
        return ret;
3,899✔
418
}
419

420
int cg_kill_kernel_sigkill(const char *path) {
1✔
421
        _cleanup_free_ char *killfile = NULL;
1✔
422
        int r;
1✔
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

427
        assert(path);
1✔
428

429
        if (!cg_kill_supported())
1✔
430
                return -EOPNOTSUPP;
431

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

436
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
1✔
437
        if (r < 0)
1✔
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) {
222,952✔
485
        char *t;
222,952✔
486

487
        assert(ret);
222,952✔
488

489
        if (isempty(path) && isempty(suffix))
236,322✔
490
                t = strdup("/sys/fs/cgroup");
1,363✔
491
        else if (isempty(path))
221,589✔
492
                t = path_join("/sys/fs/cgroup", suffix);
12,007✔
493
        else if (isempty(suffix))
209,582✔
494
                t = path_join("/sys/fs/cgroup", path);
82,565✔
495
        else
496
                t = path_join("/sys/fs/cgroup", path, suffix);
127,017✔
497
        if (!t)
222,952✔
498
                return -ENOMEM;
499

500
        *ret = t;
222,952✔
501
        return 0;
222,952✔
502
}
503

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

507
        assert(ret);
223,198✔
508

509
        if (!controller) {
223,198✔
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))
222,952✔
532
                return -EINVAL;
533

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

544
        path_simplify(*ret);
222,952✔
545
        return 0;
222,952✔
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,832✔
564
        int r;
19,832✔
565

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

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

572
        r = cg_all_unified();
19,832✔
573
        if (r < 0)
19,832✔
574
                return r;
575
        if (r > 0) {
19,832✔
576
                /* In the unified hierarchy all controllers are considered accessible,
577
                 * except for the named hierarchies */
578
                if (startswith(controller, "name="))
19,832✔
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,832✔
588
}
589

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

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

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

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

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

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

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

616
        return lgetxattr_malloc(fs, name, ret, ret_size);
15,631✔
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,446✔
634
        _cleanup_free_ char *fs = NULL;
29,446✔
635
        int r;
29,446✔
636

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

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

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

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

652
        assert(pid >= 0);
39,911✔
653
        assert(ret_path);
39,911✔
654

655
        if (controller) {
39,911✔
656
                if (!cg_controller_is_valid(controller))
39,721✔
657
                        return -EINVAL;
658
        } else
659
                controller = SYSTEMD_CGROUP_CONTROLLER;
660

661
        unified = cg_unified_controller(controller);
39,911✔
662
        if (unified < 0)
39,911✔
663
                return unified;
664
        if (unified == 0) {
39,911✔
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,079✔
672
        r = fopen_unlocked(fs, "re", &f);
39,911✔
673
        if (r == -ENOENT)
39,911✔
674
                return -ESRCH;
675
        if (r < 0)
35,784✔
676
                return r;
677

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

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

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

693
                        e = strchr(e, ':');
35,783✔
694
                        if (!e)
35,783✔
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);
35,783✔
718
                if (!path)
35,783✔
719
                        return -ENOMEM;
720

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

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

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

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

739
        assert(ret_path);
9,779✔
740

741
        if (!pidref_is_set(pidref))
9,779✔
742
                return -ESRCH;
743
        if (pidref_is_remote(pidref))
19,558✔
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,779✔
751
        if (r < 0)
9,779✔
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,777✔
756
        if (r < 0)
9,777✔
757
                return r;
758

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

763
int cg_is_empty(const char *controller, const char *path) {
2,381✔
764
        _cleanup_free_ char *t = NULL;
2,381✔
765
        int r;
2,381✔
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,381✔
771

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

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

782
        return streq(t, "0");
281✔
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) {
12,948✔
863
        char *p, *e;
12,948✔
864
        int r;
12,948✔
865

866
        assert(ret_path);
12,948✔
867

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

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

876
        *ret_path = p;
12,948✔
877
        return 0;
12,948✔
878
}
879

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

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

886
        _cleanup_free_ char *rt = NULL;
11,694✔
887
        if (!root) {
11,694✔
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,938✔
892
                if (r < 0)
1,938✔
893
                        return r;
894

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

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

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

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

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

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

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

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

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

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

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

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

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

942
        return 0;
943
}
944

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

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

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

955
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
60,574✔
956
}
957

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

961
        /* Skips over all slice assignments */
962

963
        for (;;) {
130,925✔
964
                size_t n;
86,878✔
965

966
                p += strspn(p, "/");
86,878✔
967

968
                n = strcspn(p, "/");
86,878✔
969
                if (!valid_slice_name(p, n))
86,878✔
970
                        return p;
42,831✔
971

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

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

979
        assert(path);
17,535✔
980

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

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

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

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

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

1005
                path_simplify(subgroup);
593✔
1006

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

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

1013
        return 0;
1014
}
1015

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

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

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

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

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

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

1035
        return 0;
9,591✔
1036
}
1037

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

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

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

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

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

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

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

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

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

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

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

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

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

1087
        const char *s = startswith(p, "session-");
15,136✔
1088
        if (!s)
15,136✔
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,702✔
1106
        size_t n;
15,702✔
1107

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

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

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

1115
        n = strcspn(p, "/");
15,698✔
1116
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,698✔
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,538✔
1122
        if (!unit_name)
15,538✔
1123
                return NULL;
1124

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

1128
        if (type != UNIT_NAME_INSTANCE)
15,538✔
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) &&
613✔
1135
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
114✔
1136
                return NULL;
104✔
1137

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

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

1144
        assert(path);
15,702✔
1145

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

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

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

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

1161
        assert(path);
7,879✔
1162

1163
        t = skip_user_prefix(path);
7,879✔
1164
        if (!t)
7,879✔
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);
222✔
1170
}
1171

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

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

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

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

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

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

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

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

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

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

1212
        assert(path);
8,719✔
1213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1273
        assert(path);
8,085✔
1274

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

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

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

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

1291
        return 0;
1292
}
1293

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

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

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

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

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

1313
        uid_t uid;
54✔
1314
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
54✔
1315
        if (r < 0)
54✔
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,167✔
1329
        const char *e = NULL;
16,167✔
1330

1331
        assert(p);
16,167✔
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,221✔
1337
                const char *s;
32,694✔
1338
                int n;
32,694✔
1339

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

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

1349
        if (e)
16,167✔
1350
                return cg_path_decode_unit(e, ret_slice);
15,929✔
1351

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

1355
        return 0;
1356
}
1357

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

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

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

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

1373
        t = skip_user_prefix(p);
7,823✔
1374
        if (!t)
7,823✔
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);
205✔
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,401✔
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,401✔
1401
                return true;
1402

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

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

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

1412
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
299,418✔
1413
                const char *q;
278,031✔
1414

1415
                q = startswith(p, cgroup_controller_to_string(c));
278,031✔
1416
                if (!q)
278,031✔
1417
                        continue;
278,031✔
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,179✔
1427
        _cleanup_free_ char *n = NULL;
21,179✔
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,179✔
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,172✔
1445
                if (!n)
21,172✔
1446
                        return -ENOMEM;
1447
        }
1448

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

1453
char* cg_unescape(const char *p) {
103,834✔
1454
        assert(p);
103,834✔
1455

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

1459
        if (p[0] == '_')
103,834✔
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) {
282,523✔
1470
        const char *t, *s;
282,523✔
1471

1472
        if (!p)
282,523✔
1473
                return false;
1474

1475
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
282,523✔
1476
                return true;
1477

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

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

1485
        for (t = p; *t; t++)
317,464✔
1486
                if (!strchr(CONTROLLER_VALID, *t))
268,096✔
1487
                        return false;
1488

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

1492
        return true;
1493
}
1494

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

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

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

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

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

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

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

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

1522
        while (dash) {
9,409✔
1523
                _cleanup_free_ char *escaped = NULL;
306✔
1524
                char n[dash - p + sizeof(".slice")];
306✔
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, '-'))
306✔
1536
                        return -EINVAL;
1537

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

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

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

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

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

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

1559
        *ret = TAKE_PTR(s);
9,103✔
1560
        return 0;
9,103✔
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,395✔
1588
        _cleanup_free_ char *p = NULL;
40,395✔
1589
        int r;
40,395✔
1590

1591
        assert(attribute);
40,395✔
1592

1593
        r = cg_get_path(controller, path, attribute, &p);
40,395✔
1594
        if (r < 0)
40,395✔
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,395✔
1603
}
1604

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

1609
        assert(attribute);
26,187✔
1610

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

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

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

1623
        assert(ret);
22,159✔
1624

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

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

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

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

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

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

1654
        return parse_boolean(value);
65✔
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,908✔
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,908✔
1687
        size_t n;
20,908✔
1688
        int r;
20,908✔
1689

1690
        assert(path);
20,908✔
1691
        assert(attribute);
20,908✔
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,908✔
1700
        if (r < 0)
20,908✔
1701
                return r;
1702

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

1707
        n = strv_length(keys);
18,754✔
1708
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
18,754✔
1709
                return 0;
1710
        assert(strv_is_uniq(keys));
18,754✔
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,754✔
1714
        size_t n_done = 0;
18,754✔
1715

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

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

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

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

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

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

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

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

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

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

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

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

1770
        assert(ret);
8,257✔
1771

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

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

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

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

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

1790
                if (space)
20,610✔
1791
                        s[n] = ' ';
17,008✔
1792
                memcpy(s + n + space, k, l);
20,610✔
1793
                n += space + l;
20,610✔
1794

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

1798
        assert(s);
3,602✔
1799

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

1803
        return 0;
3,602✔
1804
}
1805

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

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

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

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

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

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

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

1834
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
488✔
1835
        CGroupMask mask;
488✔
1836
        int r;
488✔
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();
488✔
1843
        if (r < 0)
488✔
1844
                return r;
488✔
1845
        if (r > 0) {
488✔
1846
                _cleanup_free_ char *controllers = NULL, *path = NULL;
488✔
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);
488✔
1852
                if (r < 0)
488✔
1853
                        return r;
1854

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

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

1863
                /* Mask controllers that are not supported in unified hierarchy. */
1864
                mask &= CGROUP_MASK_V2;
488✔
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;
488✔
1886
        return 0;
488✔
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) {
287,805✔
1913
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
287,805✔
1914

1915
        struct statfs fs;
287,805✔
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)
287,805✔
1923
                unified_cache = CGROUP_UNIFIED_UNKNOWN;
4✔
1924
        else if (unified_cache >= CGROUP_UNIFIED_NONE)
287,801✔
1925
                return unified_cache;
287,805✔
1926

1927
        if (statfs("/sys/fs/cgroup/", &fs) < 0)
13,060✔
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,060✔
1931
                log_debug("Found cgroup2 on /sys/fs/cgroup/, full unified hierarchy");
13,060✔
1932
                unified_cache = CGROUP_UNIFIED_ALL;
13,060✔
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,060✔
1971
}
1972

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

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

1980
        if (r == CGROUP_UNIFIED_NONE)
39,912✔
1981
                return false;
1982

1983
        if (r >= CGROUP_UNIFIED_ALL)
39,912✔
1984
                return true;
1985

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

1989
int cg_all_unified(void) {
247,887✔
1990
        int r;
247,887✔
1991

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

1996
        return r >= CGROUP_UNIFIED_ALL;
247,887✔
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,392✔
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);
344,189✔
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,315✔
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,407✔
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