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

systemd / systemd / 15221274416

23 May 2025 06:04PM UTC coverage: 72.07% (+0.009%) from 72.061%
15221274416

push

github

yuwata
linux: update kernel headers from v6.15-rc7

299231 of 415193 relevant lines covered (72.07%)

701304.83 hits per line

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

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

3
#include <errno.h>
4
#include <limits.h>
5
#include <signal.h>
6
#include <stddef.h>
7
#include <stdlib.h>
8
#include <sys/types.h>
9
#include <sys/utsname.h>
10
#include <sys/xattr.h>
11
#include <threads.h>
12
#include <unistd.h>
13

14
#include "alloc-util.h"
15
#include "capsule-util.h"
16
#include "cgroup-util.h"
17
#include "constants.h"
18
#include "dirent-util.h"
19
#include "errno-util.h"
20
#include "extract-word.h"
21
#include "fd-util.h"
22
#include "fileio.h"
23
#include "format-util.h"
24
#include "fs-util.h"
25
#include "log.h"
26
#include "login-util.h"
27
#include "macro.h"
28
#include "missing_fs.h"
29
#include "missing_magic.h"
30
#include "mkdir.h"
31
#include "parse-util.h"
32
#include "path-util.h"
33
#include "process-util.h"
34
#include "set.h"
35
#include "special.h"
36
#include "stat-util.h"
37
#include "stdio-util.h"
38
#include "string-table.h"
39
#include "string-util.h"
40
#include "strv.h"
41
#include "unit-name.h"
42
#include "user-util.h"
43
#include "xattr-util.h"
44

45
int cg_path_open(const char *controller, const char *path) {
790✔
46
        _cleanup_free_ char *fs = NULL;
790✔
47
        int r;
790✔
48

49
        r = cg_get_path(controller, path, /* suffix=*/ NULL, &fs);
790✔
50
        if (r < 0)
790✔
51
                return r;
52

53
        return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
790✔
54
}
55

56
int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) {
8✔
57
        _cleanup_close_ int fsfd = -EBADF;
8✔
58

59
        if (cgroupfs_fd < 0) {
8✔
60
                fsfd = open("/sys/fs/cgroup", O_CLOEXEC|O_DIRECTORY);
7✔
61
                if (fsfd < 0)
7✔
62
                        return -errno;
×
63

64
                cgroupfs_fd = fsfd;
65
        }
66

67
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
8✔
68
        CG_FILE_HANDLE_CGROUPID(fh) = id;
8✔
69

70
        return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
15✔
71
}
72

73
int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret) {
×
74
        _cleanup_close_ int cgfd = -EBADF;
×
75
        int r;
×
76

77
        cgfd = cg_cgroupid_open(cgroupfs_fd, id);
×
78
        if (cgfd < 0)
×
79
                return cgfd;
80

81
        _cleanup_free_ char *path = NULL;
×
82
        r = fd_get_path(cgfd, &path);
×
83
        if (r < 0)
×
84
                return r;
85

86
        if (!path_startswith(path, "/sys/fs/cgroup/"))
×
87
                return -EXDEV; /* recognizable error */
88

89
        if (ret)
×
90
                *ret = TAKE_PTR(path);
×
91
        return 0;
92
}
93

94
int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret) {
4,033✔
95
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
4,033✔
96
        int mnt_id;
4,033✔
97

98
        assert(dfd >= 0 || (dfd == AT_FDCWD && path_is_absolute(path)));
8,031✔
99
        assert(ret);
4,033✔
100

101
        /* This is cgroupfs so we know the size of the handle, thus no need to loop around like
102
         * name_to_handle_at_loop() does in mountpoint-util.c */
103
        if (name_to_handle_at(dfd, strempty(path), &fh.file_handle, &mnt_id, isempty(path) ? AT_EMPTY_PATH : 0) < 0) {
8,066✔
104
                assert(errno != EOVERFLOW);
×
105
                return -errno;
×
106
        }
107

108
        *ret = CG_FILE_HANDLE_CGROUPID(fh);
4,033✔
109
        return 0;
4,033✔
110
}
111

112
int cg_enumerate_processes(const char *controller, const char *path, FILE **ret) {
18,201✔
113
        _cleanup_free_ char *fs = NULL;
18,201✔
114
        FILE *f;
18,201✔
115
        int r;
18,201✔
116

117
        assert(ret);
18,201✔
118

119
        r = cg_get_path(controller, path, "cgroup.procs", &fs);
18,201✔
120
        if (r < 0)
18,201✔
121
                return r;
122

123
        f = fopen(fs, "re");
18,201✔
124
        if (!f)
18,201✔
125
                return -errno;
12,574✔
126

127
        *ret = f;
5,627✔
128
        return 0;
5,627✔
129
}
130

131
int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags) {
9,967✔
132
        unsigned long ul;
9,967✔
133

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

136
        assert(f);
9,967✔
137
        assert(ret);
9,967✔
138

139
        for (;;) {
9,967✔
140
                errno = 0;
9,967✔
141
                if (fscanf(f, "%lu", &ul) != 1) {
9,967✔
142

143
                        if (feof(f)) {
5,857✔
144
                                *ret = 0;
5,857✔
145
                                return 0;
5,857✔
146
                        }
147

148
                        return errno_or_else(EIO);
×
149
                }
150

151
                if (ul > PID_T_MAX)
4,110✔
152
                        return -EIO;
153

154
                /* In some circumstances (e.g. WSL), cgroups might contain unmappable PIDs from other
155
                 * contexts. These show up as zeros, and depending on the caller, can either be plain
156
                 * skipped over, or returned as-is. */
157
                if (ul == 0 && !FLAGS_SET(flags, CGROUP_DONT_SKIP_UNMAPPED))
4,110✔
158
                        continue;
×
159

160
                *ret = (pid_t) ul;
4,110✔
161
                return 1;
4,110✔
162
        }
163
}
164

165
int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) {
7,224✔
166
        int r;
7,224✔
167

168
        assert(f);
7,224✔
169
        assert(ret);
7,224✔
170

171
        for (;;) {
×
172
                pid_t pid;
7,224✔
173

174
                r = cg_read_pid(f, &pid, flags);
7,224✔
175
                if (r < 0)
7,224✔
176
                        return log_debug_errno(r, "Failed to read pid from cgroup item: %m");
×
177
                if (r == 0) {
7,224✔
178
                        *ret = PIDREF_NULL;
5,253✔
179
                        return 0;
5,253✔
180
                }
181

182
                if (pid == 0)
1,971✔
183
                        return -EREMOTE;
184

185
                r = pidref_set_pid(ret, pid);
1,971✔
186
                if (r >= 0)
1,971✔
187
                        return 1;
188
                if (r != -ESRCH)
×
189
                        return r;
190

191
                /* ESRCH → gone by now? just skip over it, read the next */
192
        }
193
}
194

195
int cg_read_event(
6,646✔
196
                const char *controller,
197
                const char *path,
198
                const char *event,
199
                char **ret) {
200

201
        _cleanup_free_ char *events = NULL, *content = NULL;
6,646✔
202
        int r;
6,646✔
203

204
        r = cg_get_path(controller, path, "cgroup.events", &events);
6,646✔
205
        if (r < 0)
6,646✔
206
                return r;
207

208
        r = read_full_virtual_file(events, &content, NULL);
6,646✔
209
        if (r < 0)
6,646✔
210
                return r;
211

212
        for (const char *p = content;;) {
259✔
213
                _cleanup_free_ char *line = NULL, *key = NULL;
259✔
214
                const char *q;
259✔
215

216
                r = extract_first_word(&p, &line, "\n", 0);
259✔
217
                if (r < 0)
259✔
218
                        return r;
219
                if (r == 0)
259✔
220
                        return -ENOENT;
221

222
                q = line;
259✔
223
                r = extract_first_word(&q, &key, " ", 0);
259✔
224
                if (r < 0)
259✔
225
                        return r;
226
                if (r == 0)
259✔
227
                        return -EINVAL;
228

229
                if (!streq(key, event))
259✔
230
                        continue;
×
231

232
                return strdup_to(ret, q);
259✔
233
        }
234
}
235

236
bool cg_kill_supported(void) {
×
237
        static thread_local int supported = -1;
×
238

239
        if (supported >= 0)
×
240
                return supported;
×
241

242
        if (cg_all_unified() <= 0)
×
243
                return (supported = false);
×
244

245
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
×
246
                return (supported = true);
×
247
        if (errno != ENOENT)
×
248
                log_debug_errno(errno, "Failed to check whether cgroup.kill is available, assuming not: %m");
×
249
        return (supported = false);
×
250
}
251

252
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret) {
17,824✔
253
        _cleanup_free_ char *fs = NULL;
17,824✔
254
        DIR *d;
17,824✔
255
        int r;
17,824✔
256

257
        assert(ret);
17,824✔
258

259
        /* This is not recursive! */
260

261
        r = cg_get_path(controller, path, NULL, &fs);
17,824✔
262
        if (r < 0)
17,824✔
263
                return r;
264

265
        d = opendir(fs);
17,824✔
266
        if (!d)
17,824✔
267
                return -errno;
12,574✔
268

269
        *ret = d;
5,250✔
270
        return 0;
5,250✔
271
}
272

273
int cg_read_subgroup(DIR *d, char **ret) {
6,603✔
274
        assert(d);
6,603✔
275
        assert(ret);
6,603✔
276

277
        FOREACH_DIRENT_ALL(de, d, return -errno) {
251,607✔
278
                if (de->d_type != DT_DIR)
246,127✔
279
                        continue;
234,044✔
280

281
                if (dot_or_dot_dot(de->d_name))
12,083✔
282
                        continue;
10,960✔
283

284
                return strdup_to_full(ret, de->d_name);
1,123✔
285
        }
286

287
        *ret = NULL;
5,480✔
288
        return 0;
5,480✔
289
}
290

291
int cg_kill(
17,700✔
292
                const char *path,
293
                int sig,
294
                CGroupFlags flags,
295
                Set *s,
296
                cg_kill_log_func_t log_kill,
297
                void *userdata) {
298

299
        _cleanup_set_free_ Set *allocated_set = NULL;
17,700✔
300
        int r, ret = 0;
17,700✔
301

302
        assert(path);
17,700✔
303
        assert(sig >= 0);
17,700✔
304

305
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
306
          * don't send SIGCONT on SIGKILL. */
307
        if (IN_SET(sig, SIGCONT, SIGKILL))
17,700✔
308
                flags &= ~CGROUP_SIGCONT;
4,171✔
309

310
        /* This goes through the tasks list and kills them all. This is repeated until no further processes
311
         * are added to the tasks list, to properly handle forking processes.
312
         *
313
         * When sending SIGKILL, prefer cg_kill_kernel_sigkill(), which is fully atomic. */
314

315
        if (!s) {
17,700✔
316
                s = allocated_set = set_new(NULL);
622✔
317
                if (!s)
622✔
318
                        return -ENOMEM;
319
        }
320

321
        bool done;
17,788✔
322
        do {
17,788✔
323
                _cleanup_fclose_ FILE *f = NULL;
12,574✔
324
                int ret_log_kill;
17,788✔
325

326
                done = true;
17,788✔
327

328
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f);
17,788✔
329
                if (r == -ENOENT)
17,788✔
330
                        break;
331
                if (r < 0)
5,214✔
332
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
333

334
                for (;;) {
7,102✔
335
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
7,102✔
336

337
                        r = cg_read_pidref(f, &pidref, flags);
7,102✔
338
                        if (r < 0)
7,102✔
339
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
340
                        if (r == 0)
7,102✔
341
                                break;
342

343
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
1,888✔
344
                                continue;
622✔
345

346
                        if (set_contains(s, PID_TO_PTR(pidref.pid)))
1,266✔
347
                                continue;
905✔
348

349
                        /* Ignore kernel threads to mimic the behavior of cgroup.kill. */
350
                        if (pidref_is_kernel_thread(&pidref) > 0) {
361✔
351
                                log_debug("Ignoring kernel thread with pid " PID_FMT " in cgroup '%s'", pidref.pid, path);
×
352
                                continue;
×
353
                        }
354

355
                        if (log_kill)
361✔
356
                                ret_log_kill = log_kill(&pidref, sig, userdata);
94✔
357

358
                        /* If we haven't killed this process yet, kill it */
359
                        r = pidref_kill(&pidref, sig);
361✔
360
                        if (r < 0 && r != -ESRCH)
361✔
361
                                RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path));
×
362
                        if (r >= 0) {
361✔
363
                                if (flags & CGROUP_SIGCONT)
361✔
364
                                        (void) pidref_kill(&pidref, SIGCONT);
266✔
365

366
                                if (ret == 0) {
361✔
367
                                        if (log_kill)
161✔
368
                                                ret = ret_log_kill;
369
                                        else
370
                                                ret = 1;
67✔
371
                                }
372
                        }
373

374
                        done = false;
361✔
375

376
                        r = set_put(s, PID_TO_PTR(pidref.pid));
361✔
377
                        if (r < 0)
361✔
378
                                return RET_GATHER(ret, r);
×
379
                }
380

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

384
        } while (!done);
5,214✔
385

386
        return ret;
387
}
388

389
int cg_kill_recursive(
17,076✔
390
                const char *path,
391
                int sig,
392
                CGroupFlags flags,
393
                Set *s,
394
                cg_kill_log_func_t log_kill,
395
                void *userdata) {
396

397
        _cleanup_set_free_ Set *allocated_set = NULL;
×
398
        _cleanup_closedir_ DIR *d = NULL;
17,076✔
399
        int r, ret;
17,076✔
400

401
        assert(path);
17,076✔
402
        assert(sig >= 0);
17,076✔
403

404
        if (!s) {
17,076✔
405
                s = allocated_set = set_new(NULL);
16,455✔
406
                if (!s)
16,455✔
407
                        return -ENOMEM;
408
        }
409

410
        ret = cg_kill(path, sig, flags, s, log_kill, userdata);
17,076✔
411

412
        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d);
17,076✔
413
        if (r < 0) {
17,076✔
414
                if (r != -ENOENT)
12,574✔
415
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
416

417
                return ret;
12,574✔
418
        }
419

420
        for (;;) {
4,634✔
421
                _cleanup_free_ char *fn = NULL, *p = NULL;
4,568✔
422

423
                r = cg_read_subgroup(d, &fn);
4,568✔
424
                if (r < 0) {
4,568✔
425
                        RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path));
×
426
                        break;
427
                }
428
                if (r == 0)
4,568✔
429
                        break;
430

431
                p = path_join(empty_to_root(path), fn);
66✔
432
                if (!p)
66✔
433
                        return -ENOMEM;
×
434

435
                r = cg_kill_recursive(p, sig, flags, s, log_kill, userdata);
66✔
436
                if (r < 0)
66✔
437
                        log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p);
×
438
                if (r != 0 && ret >= 0)
66✔
439
                        ret = r;
15✔
440
        }
441

442
        return ret;
4,502✔
443
}
444

445
int cg_kill_kernel_sigkill(const char *path) {
×
446
        _cleanup_free_ char *killfile = NULL;
×
447
        int r;
×
448

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

452
        assert(path);
×
453

454
        if (!cg_kill_supported())
×
455
                return -EOPNOTSUPP;
456

457
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.kill", &killfile);
×
458
        if (r < 0)
×
459
                return r;
460

461
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
×
462
        if (r < 0)
×
463
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
464

465
        return 0;
466
}
467

468
static const char *controller_to_dirname(const char *controller) {
×
469
        assert(controller);
×
470

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

475
        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
×
476
                if (cg_hybrid_unified() > 0)
×
477
                        controller = SYSTEMD_CGROUP_CONTROLLER_HYBRID;
478
                else
479
                        controller = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
×
480
        }
481

482
        return startswith(controller, "name=") ?: controller;
×
483
}
484

485
static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **ret) {
×
486
        const char *dn;
×
487
        char *t = NULL;
×
488

489
        assert(ret);
×
490
        assert(controller);
×
491

492
        dn = controller_to_dirname(controller);
×
493

494
        if (isempty(path) && isempty(suffix))
×
495
                t = path_join("/sys/fs/cgroup", dn);
×
496
        else if (isempty(path))
×
497
                t = path_join("/sys/fs/cgroup", dn, suffix);
×
498
        else if (isempty(suffix))
×
499
                t = path_join("/sys/fs/cgroup", dn, path);
×
500
        else
501
                t = path_join("/sys/fs/cgroup", dn, path, suffix);
×
502
        if (!t)
×
503
                return -ENOMEM;
504

505
        *ret = t;
×
506
        return 0;
×
507
}
508

509
static int join_path_unified(const char *path, const char *suffix, char **ret) {
236,144✔
510
        char *t;
236,144✔
511

512
        assert(ret);
236,144✔
513

514
        if (isempty(path) && isempty(suffix))
250,319✔
515
                t = strdup("/sys/fs/cgroup");
1,081✔
516
        else if (isempty(path))
235,063✔
517
                t = path_join("/sys/fs/cgroup", suffix);
13,094✔
518
        else if (isempty(suffix))
221,969✔
519
                t = path_join("/sys/fs/cgroup", path);
81,202✔
520
        else
521
                t = path_join("/sys/fs/cgroup", path, suffix);
140,767✔
522
        if (!t)
236,144✔
523
                return -ENOMEM;
524

525
        *ret = t;
236,144✔
526
        return 0;
236,144✔
527
}
528

529
int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret) {
236,401✔
530
        int r;
236,401✔
531

532
        assert(ret);
236,401✔
533

534
        if (!controller) {
236,401✔
535
                char *t;
257✔
536

537
                /* If no controller is specified, we return the path *below* the controllers, without any
538
                 * prefix. */
539

540
                if (isempty(path) && isempty(suffix))
257✔
541
                        return -EINVAL;
542

543
                if (isempty(suffix))
257✔
544
                        t = strdup(path);
×
545
                else if (isempty(path))
257✔
546
                        t = strdup(suffix);
×
547
                else
548
                        t = path_join(path, suffix);
257✔
549
                if (!t)
257✔
550
                        return -ENOMEM;
551

552
                *ret = path_simplify(t);
257✔
553
                return 0;
257✔
554
        }
555

556
        if (!cg_controller_is_valid(controller))
236,144✔
557
                return -EINVAL;
558

559
        r = cg_all_unified();
236,144✔
560
        if (r < 0)
236,144✔
561
                return r;
562
        if (r > 0)
236,144✔
563
                r = join_path_unified(path, suffix, ret);
236,144✔
564
        else
565
                r = join_path_legacy(controller, path, suffix, ret);
×
566
        if (r < 0)
236,144✔
567
                return r;
568

569
        path_simplify(*ret);
236,144✔
570
        return 0;
236,144✔
571
}
572

573
static int controller_is_v1_accessible(const char *root, const char *controller) {
×
574
        const char *cpath, *dn;
×
575

576
        assert(controller);
×
577

578
        dn = controller_to_dirname(controller);
×
579

580
        /* If root if specified, we check that:
581
         * - possible subcgroup is created at root,
582
         * - we can modify the hierarchy. */
583

584
        cpath = strjoina("/sys/fs/cgroup/", dn, root, root ? "/cgroup.procs" : NULL);
×
585
        return access_nofollow(cpath, root ? W_OK : F_OK);
×
586
}
587

588
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret) {
18,711✔
589
        int r;
18,711✔
590

591
        assert(controller);
18,711✔
592
        assert(ret);
18,711✔
593

594
        if (!cg_controller_is_valid(controller))
18,711✔
595
                return -EINVAL;
596

597
        r = cg_all_unified();
18,711✔
598
        if (r < 0)
18,711✔
599
                return r;
600
        if (r > 0) {
18,711✔
601
                /* In the unified hierarchy all controllers are considered accessible,
602
                 * except for the named hierarchies */
603
                if (startswith(controller, "name="))
18,711✔
604
                        return -EOPNOTSUPP;
605
        } else {
606
                /* Check if the specified controller is actually accessible */
607
                r = controller_is_v1_accessible(NULL, controller);
×
608
                if (r < 0)
×
609
                        return r;
610
        }
611

612
        return cg_get_path(controller, path, suffix, ret);
18,711✔
613
}
614

615
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
4,436✔
616
        _cleanup_free_ char *fs = NULL;
4,436✔
617
        int r;
4,436✔
618

619
        assert(path);
4,436✔
620
        assert(name);
4,436✔
621
        assert(value || size <= 0);
4,436✔
622

623
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
4,436✔
624
        if (r < 0)
4,436✔
625
                return r;
626

627
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
4,436✔
628
}
629

630
int cg_get_xattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size) {
15,756✔
631
        _cleanup_free_ char *fs = NULL;
15,756✔
632
        int r;
15,756✔
633

634
        assert(path);
15,756✔
635
        assert(name);
15,756✔
636

637
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
15,756✔
638
        if (r < 0)
15,756✔
639
                return r;
640

641
        return lgetxattr_malloc(fs, name, ret, ret_size);
15,756✔
642
}
643

644
int cg_get_xattr_bool(const char *path, const char *name) {
147✔
645
        _cleanup_free_ char *fs = NULL;
147✔
646
        int r;
147✔
647

648
        assert(path);
147✔
649
        assert(name);
147✔
650

651
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
147✔
652
        if (r < 0)
147✔
653
                return r;
654

655
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
147✔
656
}
657

658
int cg_remove_xattr(const char *path, const char *name) {
24,644✔
659
        _cleanup_free_ char *fs = NULL;
24,644✔
660
        int r;
24,644✔
661

662
        assert(path);
24,644✔
663
        assert(name);
24,644✔
664

665
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
24,644✔
666
        if (r < 0)
24,644✔
667
                return r;
668

669
        return RET_NERRNO(removexattr(fs, name));
49,288✔
670
}
671

672
int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
41,669✔
673
        _cleanup_fclose_ FILE *f = NULL;
41,669✔
674
        const char *fs, *controller_str = NULL;  /* avoid false maybe-uninitialized warning */
41,669✔
675
        int unified, r;
41,669✔
676

677
        assert(pid >= 0);
41,669✔
678
        assert(ret_path);
41,669✔
679

680
        if (controller) {
41,669✔
681
                if (!cg_controller_is_valid(controller))
41,485✔
682
                        return -EINVAL;
683
        } else
684
                controller = SYSTEMD_CGROUP_CONTROLLER;
685

686
        unified = cg_unified_controller(controller);
41,669✔
687
        if (unified < 0)
41,669✔
688
                return unified;
689
        if (unified == 0) {
41,669✔
690
                if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
×
691
                        controller_str = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
692
                else
693
                        controller_str = controller;
×
694
        }
695

696
        fs = procfs_file_alloca(pid, "cgroup");
48,513✔
697
        r = fopen_unlocked(fs, "re", &f);
41,669✔
698
        if (r == -ENOENT)
41,669✔
699
                return -ESRCH;
700
        if (r < 0)
37,273✔
701
                return r;
702

703
        for (;;) {
37,273✔
704
                _cleanup_free_ char *line = NULL;
37,273✔
705
                char *e;
37,273✔
706

707
                r = read_line(f, LONG_LINE_MAX, &line);
37,273✔
708
                if (r < 0)
37,273✔
709
                        return r;
710
                if (r == 0)
37,266✔
711
                        return -ENODATA;
712

713
                if (unified) {
37,266✔
714
                        e = startswith(line, "0:");
37,266✔
715
                        if (!e)
37,266✔
716
                                continue;
×
717

718
                        e = strchr(e, ':');
37,266✔
719
                        if (!e)
37,266✔
720
                                continue;
×
721
                } else {
722
                        char *l;
×
723

724
                        l = strchr(line, ':');
×
725
                        if (!l)
×
726
                                continue;
×
727

728
                        l++;
×
729
                        e = strchr(l, ':');
×
730
                        if (!e)
×
731
                                continue;
×
732
                        *e = 0;
×
733

734
                        assert(controller_str);
×
735
                        r = string_contains_word(l, ",", controller_str);
×
736
                        if (r < 0)
×
737
                                return r;
738
                        if (r == 0)
×
739
                                continue;
×
740
                }
741

742
                _cleanup_free_ char *path = strdup(e + 1);
37,266✔
743
                if (!path)
37,266✔
744
                        return -ENOMEM;
745

746
                /* Refuse cgroup paths from outside our cgroup namespace */
747
                if (startswith(path, "/../"))
37,266✔
748
                        return -EUNATCH;
749

750
                /* Truncate suffix indicating the process is a zombie */
751
                e = endswith(path, " (deleted)");
37,266✔
752
                if (e)
37,266✔
753
                        *e = 0;
257✔
754

755
                *ret_path = TAKE_PTR(path);
37,266✔
756
                return 0;
37,266✔
757
        }
758
}
759

760
int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_path) {
10,837✔
761
        _cleanup_free_ char *path = NULL;
10,837✔
762
        int r;
10,837✔
763

764
        assert(ret_path);
10,837✔
765

766
        if (!pidref_is_set(pidref))
10,837✔
767
                return -ESRCH;
768
        if (pidref_is_remote(pidref))
21,674✔
769
                return -EREMOTE;
770

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

775
        r = cg_pid_get_path(controller, pidref->pid, &path);
10,837✔
776
        if (r < 0)
10,837✔
777
                return r;
778

779
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
780
        r = pidref_verify(pidref);
10,835✔
781
        if (r < 0)
10,835✔
782
                return r;
783

784
        *ret_path = TAKE_PTR(path);
10,835✔
785
        return 0;
10,835✔
786
}
787

788
int cg_is_empty(const char *controller, const char *path) {
4✔
789
        _cleanup_fclose_ FILE *f = NULL;
4✔
790
        pid_t pid;
4✔
791
        int r;
4✔
792

793
        assert(path);
4✔
794

795
        r = cg_enumerate_processes(controller, path, &f);
4✔
796
        if (r == -ENOENT)
4✔
797
                return true;
798
        if (r < 0)
4✔
799
                return r;
800

801
        r = cg_read_pid(f, &pid, CGROUP_DONT_SKIP_UNMAPPED);
4✔
802
        if (r < 0)
4✔
803
                return r;
804

805
        return r == 0;
4✔
806
}
807

808
int cg_is_empty_recursive(const char *controller, const char *path) {
6,646✔
809
        int r;
6,646✔
810

811
        assert(path);
6,646✔
812

813
        /* The root cgroup is always populated */
814
        if (controller && empty_or_root(path))
6,646✔
815
                return false;
816

817
        r = cg_unified_controller(controller);
6,646✔
818
        if (r < 0)
6,646✔
819
                return r;
820
        if (r > 0) {
6,646✔
821
                _cleanup_free_ char *t = NULL;
6,646✔
822

823
                /* On the unified hierarchy we can check empty state
824
                 * via the "populated" attribute of "cgroup.events". */
825

826
                r = cg_read_event(controller, path, "populated", &t);
6,646✔
827
                if (r == -ENOENT)
6,646✔
828
                        return true;
829
                if (r < 0)
259✔
830
                        return r;
831

832
                return streq(t, "0");
259✔
833
        } else {
834
                _cleanup_closedir_ DIR *d = NULL;
×
835
                char *fn;
×
836

837
                r = cg_is_empty(controller, path);
×
838
                if (r <= 0)
×
839
                        return r;
840

841
                r = cg_enumerate_subgroups(controller, path, &d);
×
842
                if (r == -ENOENT)
×
843
                        return true;
844
                if (r < 0)
×
845
                        return r;
846

847
                while ((r = cg_read_subgroup(d, &fn)) > 0) {
×
848
                        _cleanup_free_ char *p = NULL;
×
849

850
                        p = path_join(path, fn);
×
851
                        free(fn);
×
852
                        if (!p)
×
853
                                return -ENOMEM;
854

855
                        r = cg_is_empty_recursive(controller, p);
×
856
                        if (r <= 0)
×
857
                                return r;
858
                }
859
                if (r < 0)
×
860
                        return r;
861

862
                return true;
×
863
        }
864
}
865

866
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
23✔
867
        _cleanup_free_ char *controller = NULL, *path = NULL;
23✔
868
        int r;
23✔
869

870
        assert(spec);
23✔
871

872
        if (*spec == '/') {
23✔
873
                if (!path_is_normalized(spec))
15✔
874
                        return -EINVAL;
875

876
                if (ret_path) {
15✔
877
                        r = path_simplify_alloc(spec, &path);
15✔
878
                        if (r < 0)
15✔
879
                                return r;
880
                }
881

882
        } else {
883
                const char *e;
8✔
884

885
                e = strchr(spec, ':');
8✔
886
                if (e) {
8✔
887
                        controller = strndup(spec, e-spec);
6✔
888
                        if (!controller)
6✔
889
                                return -ENOMEM;
890
                        if (!cg_controller_is_valid(controller))
6✔
891
                                return -EINVAL;
892

893
                        if (!isempty(e + 1)) {
3✔
894
                                path = strdup(e+1);
2✔
895
                                if (!path)
2✔
896
                                        return -ENOMEM;
897

898
                                if (!path_is_normalized(path) ||
2✔
899
                                    !path_is_absolute(path))
2✔
900
                                        return -EINVAL;
901

902
                                path_simplify(path);
1✔
903
                        }
904

905
                } else {
906
                        if (!cg_controller_is_valid(spec))
2✔
907
                                return -EINVAL;
908

909
                        if (ret_controller) {
1✔
910
                                controller = strdup(spec);
1✔
911
                                if (!controller)
1✔
912
                                        return -ENOMEM;
913
                        }
914
                }
915
        }
916

917
        if (ret_controller)
18✔
918
                *ret_controller = TAKE_PTR(controller);
18✔
919
        if (ret_path)
18✔
920
                *ret_path = TAKE_PTR(path);
18✔
921
        return 0;
922
}
923

924
int cg_mangle_path(const char *path, char **ret) {
465✔
925
        _cleanup_free_ char *c = NULL, *p = NULL;
465✔
926
        int r;
465✔
927

928
        assert(path);
465✔
929
        assert(ret);
465✔
930

931
        /* First, check if it already is a filesystem path */
932
        if (path_startswith(path, "/sys/fs/cgroup"))
465✔
933
                return path_simplify_alloc(path, ret);
461✔
934

935
        /* Otherwise, treat it as cg spec */
936
        r = cg_split_spec(path, &c, &p);
4✔
937
        if (r < 0)
4✔
938
                return r;
939

940
        return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, ret);
8✔
941
}
942

943
int cg_get_root_path(char **ret_path) {
13,777✔
944
        char *p, *e;
13,777✔
945
        int r;
13,777✔
946

947
        assert(ret_path);
13,777✔
948

949
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
13,777✔
950
        if (r < 0)
13,777✔
951
                return r;
13,777✔
952

953
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
13,777✔
954
        if (e)
13,777✔
955
                *e = 0;
13,747✔
956

957
        *ret_path = p;
13,777✔
958
        return 0;
13,777✔
959
}
960

961
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
11,216✔
962
        int r;
11,216✔
963

964
        assert(cgroup);
11,216✔
965
        assert(ret_shifted);
11,216✔
966

967
        _cleanup_free_ char *rt = NULL;
11,216✔
968
        if (!root) {
11,216✔
969
                /* If the root was specified let's use that, otherwise
970
                 * let's determine it from PID 1 */
971

972
                r = cg_get_root_path(&rt);
1,976✔
973
                if (r < 0)
1,976✔
974
                        return r;
975

976
                root = rt;
1,976✔
977
        }
978

979
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
11,216✔
980
        return 0;
11,216✔
981
}
982

983
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
15,439✔
984
        _cleanup_free_ char *raw = NULL;
15,439✔
985
        const char *c;
15,439✔
986
        int r;
15,439✔
987

988
        assert(pid >= 0);
15,439✔
989
        assert(ret_cgroup);
15,439✔
990

991
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw);
15,439✔
992
        if (r < 0)
15,439✔
993
                return r;
994

995
        r = cg_shift_path(raw, root, &c);
11,038✔
996
        if (r < 0)
11,038✔
997
                return r;
998

999
        if (c == raw) {
11,038✔
1000
                *ret_cgroup = TAKE_PTR(raw);
11,038✔
1001
                return 0;
11,038✔
1002
        }
1003

1004
        return strdup_to(ret_cgroup, c);
×
1005
}
1006

1007
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
32,223✔
1008
        assert(cgroup);
32,223✔
1009
        assert(ret_unit);
32,223✔
1010

1011
        size_t n = strcspn(cgroup, "/");
32,223✔
1012
        if (n < 3)
32,223✔
1013
                return -ENXIO;
1014

1015
        char *c = strndupa_safe(cgroup, n);
32,216✔
1016
        c = cg_unescape(c);
32,216✔
1017

1018
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
32,216✔
1019
                return -ENXIO;
1020

1021
        return strdup_to(ret_unit, c);
32,208✔
1022
}
1023

1024
static bool valid_slice_name(const char *p, size_t n) {
114,697✔
1025

1026
        if (!p)
114,697✔
1027
                return false;
1028

1029
        if (n < STRLEN("x.slice"))
114,682✔
1030
                return false;
1031

1032
        if (memcmp(p + n - 6, ".slice", 6) == 0) {
114,664✔
1033
                char buf[n+1], *c;
58,146✔
1034

1035
                memcpy(buf, p, n);
58,146✔
1036
                buf[n] = 0;
58,146✔
1037

1038
                c = cg_unescape(buf);
58,146✔
1039

1040
                return unit_name_is_valid(c, UNIT_NAME_PLAIN);
58,146✔
1041
        }
1042

1043
        return false;
1044
}
1045

1046
static const char *skip_slices(const char *p) {
40,858✔
1047
        assert(p);
40,858✔
1048

1049
        /* Skips over all slice assignments */
1050

1051
        for (;;) {
124,784✔
1052
                size_t n;
82,821✔
1053

1054
                p += strspn(p, "/");
82,821✔
1055

1056
                n = strcspn(p, "/");
82,821✔
1057
                if (!valid_slice_name(p, n))
82,821✔
1058
                        return p;
40,858✔
1059

1060
                p += n;
41,963✔
1061
        }
1062
}
1063

1064
int cg_path_get_unit(const char *path, char **ret) {
16,730✔
1065
        _cleanup_free_ char *unit = NULL;
16,730✔
1066
        const char *e;
16,730✔
1067
        int r;
16,730✔
1068

1069
        assert(path);
16,730✔
1070
        assert(ret);
16,730✔
1071

1072
        e = skip_slices(path);
16,730✔
1073

1074
        r = cg_path_decode_unit(e, &unit);
16,730✔
1075
        if (r < 0)
16,730✔
1076
                return r;
1077

1078
        /* We skipped over the slices, don't accept any now */
1079
        if (endswith(unit, ".slice"))
16,719✔
1080
                return -ENXIO;
1081

1082
        *ret = TAKE_PTR(unit);
16,719✔
1083
        return 0;
16,719✔
1084
}
1085

1086
int cg_path_get_unit_path(const char *path, char **ret) {
9,078✔
1087
        _cleanup_free_ char *path_copy = NULL;
9,078✔
1088
        char *unit_name;
9,078✔
1089

1090
        assert(path);
9,078✔
1091
        assert(ret);
9,078✔
1092

1093
        path_copy = strdup(path);
9,078✔
1094
        if (!path_copy)
9,078✔
1095
                return -ENOMEM;
1096

1097
        unit_name = (char *)skip_slices(path_copy);
9,078✔
1098
        unit_name[strcspn(unit_name, "/")] = 0;
9,078✔
1099

1100
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,078✔
1101
                return -ENXIO;
1102

1103
        *ret = TAKE_PTR(path_copy);
9,075✔
1104

1105
        return 0;
9,075✔
1106
}
1107

1108
int cg_pid_get_unit(pid_t pid, char **ret_unit) {
623✔
1109
        _cleanup_free_ char *cgroup = NULL;
623✔
1110
        int r;
623✔
1111

1112
        assert(ret_unit);
623✔
1113

1114
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
623✔
1115
        if (r < 0)
623✔
1116
                return r;
1117

1118
        return cg_path_get_unit(cgroup, ret_unit);
617✔
1119
}
1120

1121
int cg_pidref_get_unit(const PidRef *pidref, char **ret) {
547✔
1122
        _cleanup_free_ char *unit = NULL;
547✔
1123
        int r;
547✔
1124

1125
        assert(ret);
547✔
1126

1127
        if (!pidref_is_set(pidref))
547✔
1128
                return -ESRCH;
1129
        if (pidref_is_remote(pidref))
1,094✔
1130
                return -EREMOTE;
1131

1132
        r = cg_pid_get_unit(pidref->pid, &unit);
547✔
1133
        if (r < 0)
547✔
1134
                return r;
1135

1136
        r = pidref_verify(pidref);
541✔
1137
        if (r < 0)
541✔
1138
                return r;
1139

1140
        *ret = TAKE_PTR(unit);
541✔
1141
        return 0;
541✔
1142
}
1143

1144
/**
1145
 * Skip session-*.scope, but require it to be there.
1146
 */
1147
static const char *skip_session(const char *p) {
14,669✔
1148
        size_t n;
14,669✔
1149

1150
        if (isempty(p))
14,669✔
1151
                return NULL;
1152

1153
        p += strspn(p, "/");
14,665✔
1154

1155
        n = strcspn(p, "/");
14,665✔
1156
        if (n < STRLEN("session-x.scope"))
14,665✔
1157
                return NULL;
1158

1159
        if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) {
14,509✔
1160
                char buf[n - 8 - 6 + 1];
21✔
1161

1162
                memcpy(buf, p + 8, n - 8 - 6);
21✔
1163
                buf[n - 8 - 6] = 0;
21✔
1164

1165
                /* Note that session scopes never need unescaping,
1166
                 * since they cannot conflict with the kernel's own
1167
                 * names, hence we don't need to call cg_unescape()
1168
                 * here. */
1169

1170
                if (!session_id_valid(buf))
21✔
1171
                        return NULL;
21✔
1172

1173
                p += n;
21✔
1174
                p += strspn(p, "/");
21✔
1175
                return p;
21✔
1176
        }
1177

1178
        return NULL;
1179
}
1180

1181
/**
1182
 * Skip user@*.service or capsule@*.service, but require either of them to be there.
1183
 */
1184
static const char *skip_user_manager(const char *p) {
15,050✔
1185
        size_t n;
15,050✔
1186

1187
        if (isempty(p))
15,050✔
1188
                return NULL;
15,050✔
1189

1190
        p += strspn(p, "/");
15,046✔
1191

1192
        n = strcspn(p, "/");
15,046✔
1193
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,046✔
1194
                return NULL;
1195

1196
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
1197
         * resolve user/capsule name. */
1198
        _cleanup_free_ char *unit_name = strndup(p, n);
14,890✔
1199
        if (!unit_name)
14,890✔
1200
                return NULL;
1201

1202
        _cleanup_free_ char *i = NULL;
14,890✔
1203
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
14,890✔
1204

1205
        if (type != UNIT_NAME_INSTANCE)
14,890✔
1206
                return NULL;
1207

1208
        /* Note that user manager services never need unescaping, since they cannot conflict with the
1209
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
1210
         * instance names, they should be always valid as we validate them upon unit start. */
1211
        if (startswith(unit_name, "user@")) {
465✔
1212
                if (parse_uid(i, NULL) < 0)
376✔
1213
                        return NULL;
1214

1215
                p += n;
376✔
1216
                p += strspn(p, "/");
376✔
1217
                return p;
376✔
1218
        } else if (startswith(unit_name, "capsule@")) {
89✔
1219
                if (capsule_name_is_valid(i) <= 0)
5✔
1220
                        return NULL;
1221

1222
                p += n;
5✔
1223
                p += strspn(p, "/");
5✔
1224
                return p;
5✔
1225
        }
1226

1227
        return NULL;
1228
}
1229

1230
static const char *skip_user_prefix(const char *path) {
15,050✔
1231
        const char *e, *t;
15,050✔
1232

1233
        assert(path);
15,050✔
1234

1235
        /* Skip slices, if there are any */
1236
        e = skip_slices(path);
15,050✔
1237

1238
        /* Skip the user manager, if it's in the path now... */
1239
        t = skip_user_manager(e);
15,050✔
1240
        if (t)
15,050✔
1241
                return t;
1242

1243
        /* Alternatively skip the user session if it is in the path... */
1244
        return skip_session(e);
14,669✔
1245
}
1246

1247
int cg_path_get_user_unit(const char *path, char **ret) {
7,550✔
1248
        const char *t;
7,550✔
1249

1250
        assert(path);
7,550✔
1251
        assert(ret);
7,550✔
1252

1253
        t = skip_user_prefix(path);
7,550✔
1254
        if (!t)
7,550✔
1255
                return -ENXIO;
1256

1257
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
1258
         * parser. */
1259
        return cg_path_get_unit(t, ret);
207✔
1260
}
1261

1262
int cg_pid_get_user_unit(pid_t pid, char **ret_unit) {
51✔
1263
        _cleanup_free_ char *cgroup = NULL;
51✔
1264
        int r;
51✔
1265

1266
        assert(ret_unit);
51✔
1267

1268
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
51✔
1269
        if (r < 0)
51✔
1270
                return r;
1271

1272
        return cg_path_get_user_unit(cgroup, ret_unit);
51✔
1273
}
1274

1275
int cg_path_get_machine_name(const char *path, char **ret_machine) {
35✔
1276
        _cleanup_free_ char *u = NULL;
35✔
1277
        const char *sl;
35✔
1278
        int r;
35✔
1279

1280
        r = cg_path_get_unit(path, &u);
35✔
1281
        if (r < 0)
35✔
1282
                return r;
1283

1284
        sl = strjoina("/run/systemd/machines/unit:", u);
175✔
1285
        return readlink_malloc(sl, ret_machine);
35✔
1286
}
1287

1288
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
35✔
1289
        _cleanup_free_ char *cgroup = NULL;
35✔
1290
        int r;
35✔
1291

1292
        assert(ret_machine);
35✔
1293

1294
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
35✔
1295
        if (r < 0)
35✔
1296
                return r;
1297

1298
        return cg_path_get_machine_name(cgroup, ret_machine);
35✔
1299
}
1300

1301
int cg_path_get_session(const char *path, char **ret_session) {
8,319✔
1302
        _cleanup_free_ char *unit = NULL;
8,319✔
1303
        char *start, *end;
8,319✔
1304
        int r;
8,319✔
1305

1306
        assert(path);
8,319✔
1307

1308
        r = cg_path_get_unit(path, &unit);
8,319✔
1309
        if (r < 0)
8,319✔
1310
                return r;
1311

1312
        start = startswith(unit, "session-");
8,318✔
1313
        if (!start)
8,318✔
1314
                return -ENXIO;
1315
        end = endswith(start, ".scope");
324✔
1316
        if (!end)
324✔
1317
                return -ENXIO;
1318

1319
        *end = 0;
324✔
1320
        if (!session_id_valid(start))
324✔
1321
                return -ENXIO;
1322

1323
        if (!ret_session)
323✔
1324
                return 0;
1325

1326
        return strdup_to(ret_session, start);
323✔
1327
}
1328

1329
int cg_pid_get_session(pid_t pid, char **ret_session) {
759✔
1330
        _cleanup_free_ char *cgroup = NULL;
759✔
1331
        int r;
759✔
1332

1333
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
759✔
1334
        if (r < 0)
759✔
1335
                return r;
1336

1337
        return cg_path_get_session(cgroup, ret_session);
759✔
1338
}
1339

1340
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
333✔
1341
        int r;
333✔
1342

1343
        if (!pidref_is_set(pidref))
333✔
1344
                return -ESRCH;
333✔
1345
        if (pidref_is_remote(pidref))
666✔
1346
                return -EREMOTE;
1347

1348
        _cleanup_free_ char *session = NULL;
333✔
1349
        r = cg_pid_get_session(pidref->pid, &session);
333✔
1350
        if (r < 0)
333✔
1351
                return r;
1352

1353
        r = pidref_verify(pidref);
283✔
1354
        if (r < 0)
283✔
1355
                return r;
1356

1357
        if (ret)
283✔
1358
                *ret = TAKE_PTR(session);
283✔
1359
        return 0;
1360
}
1361

1362
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
7,950✔
1363
        _cleanup_free_ char *slice = NULL;
7,950✔
1364
        char *start, *end;
7,950✔
1365
        int r;
7,950✔
1366

1367
        assert(path);
7,950✔
1368

1369
        r = cg_path_get_slice(path, &slice);
7,950✔
1370
        if (r < 0)
7,950✔
1371
                return r;
1372

1373
        start = startswith(slice, "user-");
7,950✔
1374
        if (!start)
7,950✔
1375
                return -ENXIO;
1376

1377
        end = endswith(start, ".slice");
450✔
1378
        if (!end)
450✔
1379
                return -ENXIO;
1380

1381
        *end = 0;
450✔
1382
        if (parse_uid(start, ret_uid) < 0)
450✔
1383
                return -ENXIO;
×
1384

1385
        return 0;
1386
}
1387

1388
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
409✔
1389
        _cleanup_free_ char *cgroup = NULL;
409✔
1390
        int r;
409✔
1391

1392
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
409✔
1393
        if (r < 0)
409✔
1394
                return r;
1395

1396
        return cg_path_get_owner_uid(cgroup, ret_uid);
409✔
1397
}
1398

1399
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
48✔
1400
        int r;
48✔
1401

1402
        if (!pidref_is_set(pidref))
48✔
1403
                return -ESRCH;
48✔
1404
        if (pidref_is_remote(pidref))
48✔
1405
                return -EREMOTE;
1406

1407
        uid_t uid;
48✔
1408
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
48✔
1409
        if (r < 0)
48✔
1410
                return r;
1411

1412
        r = pidref_verify(pidref);
8✔
1413
        if (r < 0)
8✔
1414
                return r;
1415

1416
        if (ret)
8✔
1417
                *ret = uid;
8✔
1418

1419
        return 0;
1420
}
1421

1422
int cg_path_get_slice(const char *p, char **ret_slice) {
15,693✔
1423
        const char *e = NULL;
15,693✔
1424

1425
        assert(p);
15,693✔
1426
        assert(ret_slice);
15,693✔
1427

1428
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1429
         * the first non-slice unit. */
1430

1431
        for (;;) {
48,059✔
1432
                const char *s;
31,876✔
1433
                int n;
31,876✔
1434

1435
                n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s);
31,876✔
1436
                if (n < 0)
31,876✔
1437
                        return n;
×
1438
                if (!valid_slice_name(s, n))
31,876✔
1439
                        break;
1440

1441
                e = s;
16,183✔
1442
        }
1443

1444
        if (e)
15,693✔
1445
                return cg_path_decode_unit(e, ret_slice);
15,484✔
1446

1447
        return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
209✔
1448
}
1449

1450
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
55✔
1451
        _cleanup_free_ char *cgroup = NULL;
55✔
1452
        int r;
55✔
1453

1454
        assert(ret_slice);
55✔
1455

1456
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
55✔
1457
        if (r < 0)
55✔
1458
                return r;
1459

1460
        return cg_path_get_slice(cgroup, ret_slice);
55✔
1461
}
1462

1463
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,500✔
1464
        const char *t;
7,500✔
1465
        assert(p);
7,500✔
1466
        assert(ret_slice);
7,500✔
1467

1468
        t = skip_user_prefix(p);
7,500✔
1469
        if (!t)
7,500✔
1470
                return -ENXIO;
1471

1472
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1473
         * from here on. */
1474
        return cg_path_get_slice(t, ret_slice);
195✔
1475
}
1476

1477
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1478
        _cleanup_free_ char *cgroup = NULL;
1✔
1479
        int r;
1✔
1480

1481
        assert(ret_slice);
1✔
1482

1483
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1✔
1484
        if (r < 0)
1✔
1485
                return r;
1486

1487
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1488
}
1489

1490
bool cg_needs_escape(const char *p) {
12,670✔
1491

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

1497
        if (!filename_is_valid(p))
12,670✔
1498
                return true;
1499

1500
        if (IN_SET(p[0], '_', '.'))
12,666✔
1501
                return true;
1502

1503
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
12,660✔
1504
                return true;
2✔
1505

1506
        if (startswith(p, "cgroup."))
12,658✔
1507
                return true;
1508

1509
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
177,184✔
1510
                const char *q;
164,528✔
1511

1512
                q = startswith(p, cgroup_controller_to_string(c));
164,528✔
1513
                if (!q)
164,528✔
1514
                        continue;
164,528✔
1515

1516
                if (q[0] == '.')
×
1517
                        return true;
1518
        }
1519

1520
        return false;
1521
}
1522

1523
int cg_escape(const char *p, char **ret) {
12,417✔
1524
        _cleanup_free_ char *n = NULL;
12,417✔
1525

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

1531
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1532

1533
        if (cg_needs_escape(p)) {
12,417✔
1534
                n = strjoin("_", p);
7✔
1535
                if (!n)
7✔
1536
                        return -ENOMEM;
1537

1538
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1539
                        return -EINVAL;
1540
        } else {
1541
                n = strdup(p);
12,410✔
1542
                if (!n)
12,410✔
1543
                        return -ENOMEM;
1544
        }
1545

1546
        *ret = TAKE_PTR(n);
12,417✔
1547
        return 0;
12,417✔
1548
}
1549

1550
char* cg_unescape(const char *p) {
99,663✔
1551
        assert(p);
99,663✔
1552

1553
        /* The return value of this function (unlike cg_escape())
1554
         * doesn't need free()! */
1555

1556
        if (p[0] == '_')
99,663✔
1557
                return (char*) p+1;
14✔
1558

1559
        return (char*) p;
1560
}
1561

1562
#define CONTROLLER_VALID                        \
1563
        DIGITS LETTERS                          \
1564
        "_"
1565

1566
bool cg_controller_is_valid(const char *p) {
296,358✔
1567
        const char *t, *s;
296,358✔
1568

1569
        if (!p)
296,358✔
1570
                return false;
1571

1572
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
296,358✔
1573
                return true;
1574

1575
        s = startswith(p, "name=");
91,109✔
1576
        if (s)
91,109✔
1577
                p = s;
2✔
1578

1579
        if (IN_SET(*p, 0, '_'))
91,109✔
1580
                return false;
1581

1582
        for (t = p; *t; t++)
585,257✔
1583
                if (!strchr(CONTROLLER_VALID, *t))
494,159✔
1584
                        return false;
1585

1586
        if (t - p > NAME_MAX)
91,098✔
1587
                return false;
×
1588

1589
        return true;
1590
}
1591

1592
int cg_slice_to_path(const char *unit, char **ret) {
5,022✔
1593
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
5,022✔
1594
        const char *dash;
5,022✔
1595
        int r;
5,022✔
1596

1597
        assert(unit);
5,022✔
1598
        assert(ret);
5,022✔
1599

1600
        if (streq(unit, SPECIAL_ROOT_SLICE))
5,022✔
1601
                return strdup_to(ret, "");
7✔
1602

1603
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
5,015✔
1604
                return -EINVAL;
1605

1606
        if (!endswith(unit, ".slice"))
5,004✔
1607
                return -EINVAL;
1608

1609
        r = unit_name_to_prefix(unit, &p);
5,003✔
1610
        if (r < 0)
5,003✔
1611
                return r;
1612

1613
        dash = strchr(p, '-');
5,003✔
1614

1615
        /* Don't allow initial dashes */
1616
        if (dash == p)
5,003✔
1617
                return -EINVAL;
1618

1619
        while (dash) {
5,164✔
1620
                _cleanup_free_ char *escaped = NULL;
166✔
1621
                char n[dash - p + sizeof(".slice")];
166✔
1622

1623
#if HAS_FEATURE_MEMORY_SANITIZER
1624
                /* msan doesn't instrument stpncpy, so it thinks
1625
                 * n is later used uninitialized:
1626
                 * https://github.com/google/sanitizers/issues/926
1627
                 */
1628
                zero(n);
1629
#endif
1630

1631
                /* Don't allow trailing or double dashes */
1632
                if (IN_SET(dash[1], 0, '-'))
166✔
1633
                        return -EINVAL;
1634

1635
                strcpy(stpncpy(n, p, dash - p), ".slice");
164✔
1636
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
164✔
1637
                        return -EINVAL;
1638

1639
                r = cg_escape(n, &escaped);
164✔
1640
                if (r < 0)
164✔
1641
                        return r;
1642

1643
                if (!strextend(&s, escaped, "/"))
164✔
1644
                        return -ENOMEM;
1645

1646
                dash = strchr(dash+1, '-');
164✔
1647
        }
1648

1649
        r = cg_escape(unit, &e);
4,998✔
1650
        if (r < 0)
4,998✔
1651
                return r;
1652

1653
        if (!strextend(&s, e))
4,998✔
1654
                return -ENOMEM;
1655

1656
        *ret = TAKE_PTR(s);
4,998✔
1657
        return 0;
4,998✔
1658
}
1659

1660
int cg_is_threaded(const char *path) {
×
1661
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
1662
        _cleanup_strv_free_ char **v = NULL;
×
1663
        int r;
×
1664

1665
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.type", &fs);
×
1666
        if (r < 0)
×
1667
                return r;
1668

1669
        r = read_full_virtual_file(fs, &contents, NULL);
×
1670
        if (r == -ENOENT)
×
1671
                return false; /* Assume no. */
1672
        if (r < 0)
×
1673
                return r;
1674

1675
        v = strv_split(contents, NULL);
×
1676
        if (!v)
×
1677
                return -ENOMEM;
1678

1679
        /* If the cgroup is in the threaded mode, it contains "threaded".
1680
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
1681
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1682
}
1683

1684
int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) {
34,278✔
1685
        _cleanup_free_ char *p = NULL;
34,278✔
1686
        int r;
34,278✔
1687

1688
        r = cg_get_path(controller, path, attribute, &p);
34,278✔
1689
        if (r < 0)
34,278✔
1690
                return r;
1691

1692
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER);
34,278✔
1693
}
1694

1695
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) {
28,526✔
1696
        _cleanup_free_ char *p = NULL;
28,526✔
1697
        int r;
28,526✔
1698

1699
        r = cg_get_path(controller, path, attribute, &p);
28,526✔
1700
        if (r < 0)
28,526✔
1701
                return r;
1702

1703
        return read_one_line_file(p, ret);
28,526✔
1704
}
1705

1706
int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret) {
23,969✔
1707
        _cleanup_free_ char *value = NULL;
23,969✔
1708
        uint64_t v;
23,969✔
1709
        int r;
23,969✔
1710

1711
        assert(ret);
23,969✔
1712

1713
        r = cg_get_attribute(controller, path, attribute, &value);
23,969✔
1714
        if (r == -ENOENT)
23,969✔
1715
                return -ENODATA;
1716
        if (r < 0)
22,352✔
1717
                return r;
1718

1719
        if (streq(value, "max")) {
22,352✔
1720
                *ret = CGROUP_LIMIT_MAX;
5,403✔
1721
                return 0;
5,403✔
1722
        }
1723

1724
        r = safe_atou64(value, &v);
16,949✔
1725
        if (r < 0)
16,949✔
1726
                return r;
1727

1728
        *ret = v;
16,949✔
1729
        return 0;
16,949✔
1730
}
1731

1732
int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret) {
64✔
1733
        _cleanup_free_ char *value = NULL;
64✔
1734
        int r;
64✔
1735

1736
        assert(ret);
64✔
1737

1738
        r = cg_get_attribute(controller, path, attribute, &value);
64✔
1739
        if (r == -ENOENT)
64✔
1740
                return -ENODATA;
1741
        if (r < 0)
64✔
1742
                return r;
1743

1744
        r = parse_boolean(value);
64✔
1745
        if (r < 0)
64✔
1746
                return r;
1747

1748
        *ret = r;
64✔
1749
        return 0;
64✔
1750
}
1751

1752
int cg_get_owner(const char *path, uid_t *ret_uid) {
35✔
1753
        _cleanup_free_ char *f = NULL;
35✔
1754
        struct stat stats;
35✔
1755
        int r;
35✔
1756

1757
        assert(ret_uid);
35✔
1758

1759
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &f);
35✔
1760
        if (r < 0)
35✔
1761
                return r;
1762

1763
        if (stat(f, &stats) < 0)
35✔
1764
                return -errno;
16✔
1765

1766
        r = stat_verify_directory(&stats);
19✔
1767
        if (r < 0)
19✔
1768
                return r;
1769

1770
        *ret_uid = stats.st_uid;
19✔
1771
        return 0;
19✔
1772
}
1773

1774
int cg_get_keyed_attribute(
26,742✔
1775
                const char *controller,
1776
                const char *path,
1777
                const char *attribute,
1778
                char * const *keys,
1779
                char **values) {
1780

1781
        _cleanup_free_ char *filename = NULL, *contents = NULL;
26,742✔
1782
        size_t n;
26,742✔
1783
        int r;
26,742✔
1784

1785
        assert(path);
26,742✔
1786
        assert(attribute);
26,742✔
1787

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

1794
        r = cg_get_path(controller, path, attribute, &filename);
26,742✔
1795
        if (r < 0)
26,742✔
1796
                return r;
1797

1798
        r = read_full_file(filename, &contents, /* ret_size = */ NULL);
26,742✔
1799
        if (r < 0)
26,742✔
1800
                return r;
1801

1802
        n = strv_length(keys);
20,200✔
1803
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
20,200✔
1804
                return 0;
1805
        assert(strv_is_uniq(keys));
20,200✔
1806

1807
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1808
        char **v = newa0(char*, n);
20,200✔
1809
        size_t n_done = 0;
20,200✔
1810

1811
        for (const char *p = contents; *p;) {
68,937✔
1812
                const char *w;
1813
                size_t i;
1814

1815
                for (i = 0; i < n; i++) {
117,674✔
1816
                        w = first_word(p, keys[i]);
75,910✔
1817
                        if (w)
75,910✔
1818
                                break;
1819
                }
1820

1821
                if (w) {
68,937✔
1822
                        if (v[i]) { /* duplicate entry? */
27,173✔
1823
                                r = -EBADMSG;
×
1824
                                goto fail;
×
1825
                        }
1826

1827
                        size_t l = strcspn(w, NEWLINE);
27,173✔
1828

1829
                        v[i] = strndup(w, l);
27,173✔
1830
                        if (!v[i]) {
27,173✔
1831
                                r = -ENOMEM;
×
1832
                                goto fail;
×
1833
                        }
1834

1835
                        n_done++;
27,173✔
1836
                        if (n_done >= n)
27,173✔
1837
                                break;
1838

1839
                        p = w + l;
6,973✔
1840
                } else
1841
                        p += strcspn(p, NEWLINE);
41,764✔
1842

1843
                p += strspn(p, NEWLINE);
48,737✔
1844
        }
1845

1846
        if (n_done < n) {
20,200✔
1847
                r = -ENXIO;
×
1848
                goto fail;
×
1849
        }
1850

1851
        memcpy(values, v, sizeof(char*) * n);
20,200✔
1852
        return 0;
20,200✔
1853

1854
fail:
×
1855
        free_many_charp(v, n);
26,742✔
1856
        return r;
1857
}
1858

1859
int cg_mask_to_string(CGroupMask mask, char **ret) {
12,015✔
1860
        _cleanup_free_ char *s = NULL;
12,015✔
1861
        bool space = false;
12,015✔
1862
        CGroupController c;
12,015✔
1863
        size_t n = 0;
12,015✔
1864

1865
        assert(ret);
12,015✔
1866

1867
        if (mask == 0) {
12,015✔
1868
                *ret = NULL;
4,929✔
1869
                return 0;
4,929✔
1870
        }
1871

1872
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
99,204✔
1873
                const char *k;
92,118✔
1874
                size_t l;
92,118✔
1875

1876
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
92,118✔
1877
                        continue;
32,712✔
1878

1879
                k = cgroup_controller_to_string(c);
59,406✔
1880
                l = strlen(k);
59,406✔
1881

1882
                if (!GREEDY_REALLOC(s, n + space + l + 1))
59,406✔
1883
                        return -ENOMEM;
1884

1885
                if (space)
59,406✔
1886
                        s[n] = ' ';
52,320✔
1887
                memcpy(s + n + space, k, l);
59,406✔
1888
                n += space + l;
59,406✔
1889

1890
                space = true;
59,406✔
1891
        }
1892

1893
        assert(s);
7,086✔
1894

1895
        s[n] = 0;
7,086✔
1896
        *ret = TAKE_PTR(s);
7,086✔
1897

1898
        return 0;
7,086✔
1899
}
1900

1901
int cg_mask_from_string(const char *value, CGroupMask *ret) {
6,492✔
1902
        CGroupMask m = 0;
6,492✔
1903

1904
        assert(ret);
6,492✔
1905
        assert(value);
6,492✔
1906

1907
        for (;;) {
58,418✔
1908
                _cleanup_free_ char *n = NULL;
51,926✔
1909
                CGroupController v;
58,418✔
1910
                int r;
58,418✔
1911

1912
                r = extract_first_word(&value, &n, NULL, 0);
58,418✔
1913
                if (r < 0)
58,418✔
1914
                        return r;
×
1915
                if (r == 0)
58,418✔
1916
                        break;
1917

1918
                v = cgroup_controller_from_string(n);
51,926✔
1919
                if (v < 0)
51,926✔
1920
                        continue;
642✔
1921

1922
                m |= CGROUP_CONTROLLER_TO_MASK(v);
51,284✔
1923
        }
1924

1925
        *ret = m;
6,492✔
1926
        return 0;
6,492✔
1927
}
1928

1929
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
464✔
1930
        CGroupMask mask;
464✔
1931
        int r;
464✔
1932

1933
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1934
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1935
         * pseudo-controllers. */
1936

1937
        r = cg_all_unified();
464✔
1938
        if (r < 0)
464✔
1939
                return r;
464✔
1940
        if (r > 0) {
464✔
1941
                _cleanup_free_ char *controllers = NULL, *path = NULL;
464✔
1942

1943
                /* In the unified hierarchy we can read the supported and accessible controllers from
1944
                 * the top-level cgroup attribute */
1945

1946
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path);
464✔
1947
                if (r < 0)
464✔
1948
                        return r;
1949

1950
                r = read_one_line_file(path, &controllers);
464✔
1951
                if (r < 0)
464✔
1952
                        return r;
1953

1954
                r = cg_mask_from_string(controllers, &mask);
464✔
1955
                if (r < 0)
464✔
1956
                        return r;
1957

1958
                /* Mask controllers that are not supported in unified hierarchy. */
1959
                mask &= CGROUP_MASK_V2;
464✔
1960

1961
        } else {
1962
                CGroupController c;
×
1963

1964
                /* In the legacy hierarchy, we check which hierarchies are accessible. */
1965

1966
                mask = 0;
×
1967
                for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
×
1968
                        CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
×
1969
                        const char *n;
×
1970

1971
                        if (!FLAGS_SET(CGROUP_MASK_V1, bit))
×
1972
                                continue;
×
1973

1974
                        n = cgroup_controller_to_string(c);
×
1975
                        if (controller_is_v1_accessible(root, n) >= 0)
×
1976
                                mask |= bit;
×
1977
                }
1978
        }
1979

1980
        *ret = mask;
464✔
1981
        return 0;
464✔
1982
}
1983

1984
int cg_mask_supported(CGroupMask *ret) {
218✔
1985
        _cleanup_free_ char *root = NULL;
218✔
1986
        int r;
218✔
1987

1988
        r = cg_get_root_path(&root);
218✔
1989
        if (r < 0)
218✔
1990
                return r;
1991

1992
        return cg_mask_supported_subtree(root, ret);
218✔
1993
}
1994

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

2007
int cg_unified_cached(bool flush) {
308,762✔
2008
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
308,762✔
2009

2010
        struct statfs fs;
308,762✔
2011

2012
        /* Checks if we support the unified hierarchy. Returns an
2013
         * error when the cgroup hierarchies aren't mounted yet or we
2014
         * have any other trouble determining if the unified hierarchy
2015
         * is supported. */
2016

2017
        if (flush)
308,762✔
2018
                unified_cache = CGROUP_UNIFIED_UNKNOWN;
4✔
2019
        else if (unified_cache >= CGROUP_UNIFIED_NONE)
308,758✔
2020
                return unified_cache;
308,762✔
2021

2022
        if (statfs("/sys/fs/cgroup/", &fs) < 0)
12,967✔
2023
                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/\") failed: %m");
×
2024

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

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

2065
        return unified_cache;
12,967✔
2066
}
2067

2068
int cg_unified_controller(const char *controller) {
48,316✔
2069
        int r;
48,316✔
2070

2071
        r = cg_unified_cached(false);
48,316✔
2072
        if (r < 0)
48,316✔
2073
                return r;
2074

2075
        if (r == CGROUP_UNIFIED_NONE)
48,316✔
2076
                return false;
2077

2078
        if (r >= CGROUP_UNIFIED_ALL)
48,316✔
2079
                return true;
2080

2081
        return streq_ptr(controller, SYSTEMD_CGROUP_CONTROLLER);
×
2082
}
2083

2084
int cg_all_unified(void) {
260,440✔
2085
        int r;
260,440✔
2086

2087
        r = cg_unified_cached(false);
260,440✔
2088
        if (r < 0)
260,440✔
2089
                return r;
2090

2091
        return r >= CGROUP_UNIFIED_ALL;
260,440✔
2092
}
2093

2094
int cg_hybrid_unified(void) {
1✔
2095
        int r;
1✔
2096

2097
        r = cg_unified_cached(false);
1✔
2098
        if (r < 0)
1✔
2099
                return r;
2100

2101
        return r == CGROUP_UNIFIED_SYSTEMD && !unified_systemd_v232;
1✔
2102
}
2103

2104
int cg_is_delegated(const char *path) {
19✔
2105
        int r;
19✔
2106

2107
        assert(path);
19✔
2108

2109
        r = cg_get_xattr_bool(path, "trusted.delegate");
19✔
2110
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
19✔
2111
                return r;
2112

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

2120
int cg_is_delegated_fd(int fd) {
214✔
2121
        int r;
214✔
2122

2123
        assert(fd >= 0);
214✔
2124

2125
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
214✔
2126
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
214✔
2127
                return r;
2128

2129
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
200✔
2130
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
200✔
2131
}
2132

2133
int cg_has_coredump_receive(const char *path) {
2✔
2134
        int r;
2✔
2135

2136
        assert(path);
2✔
2137

2138
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
2139
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
2140
                return false;
×
2141

2142
        return r;
2143
}
2144

2145
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2146
        [CGROUP_IO_RBPS_MAX]    = CGROUP_LIMIT_MAX,
2147
        [CGROUP_IO_WBPS_MAX]    = CGROUP_LIMIT_MAX,
2148
        [CGROUP_IO_RIOPS_MAX]   = CGROUP_LIMIT_MAX,
2149
        [CGROUP_IO_WIOPS_MAX]   = CGROUP_LIMIT_MAX,
2150
};
2151

2152
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2153
        [CGROUP_IO_RBPS_MAX]    = "IOReadBandwidthMax",
2154
        [CGROUP_IO_WBPS_MAX]    = "IOWriteBandwidthMax",
2155
        [CGROUP_IO_RIOPS_MAX]   = "IOReadIOPSMax",
2156
        [CGROUP_IO_WIOPS_MAX]   = "IOWriteIOPSMax",
2157
};
2158

2159
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
5,262✔
2160

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

2177
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
297,048✔
2178

2179
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
2180
        [MANAGED_OOM_AUTO] = "auto",
2181
        [MANAGED_OOM_KILL] = "kill",
2182
};
2183

2184
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
31,413✔
2185

2186
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
2187
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
2188
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
2189
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
2190
};
2191

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