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

systemd / systemd / 15720680019

17 Jun 2025 07:58PM UTC coverage: 72.087% (-0.02%) from 72.109%
15720680019

push

github

web-flow
sd-lldp: several improvements (#37845)

This makes
- sd-lldp-tx not send machine ID as chassis ID, but use application
specific machine ID,
- sd-lldp-tx emit vlan ID if it is running on a vlan interface,
- Describe() DBus method also reply LLDP configurations,
- io.systemd.Network.GetLLDPNeighbors varlink method provides vlan ID,
if received.

Closes #37613.

59 of 76 new or added lines in 3 files covered. (77.63%)

4663 existing lines in 75 files now uncovered.

300494 of 416851 relevant lines covered (72.09%)

704683.58 hits per line

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

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

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

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

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

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

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

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

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

59
        return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
818✔
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,040✔
101
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
4,040✔
102
        int mnt_id;
4,040✔
103

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

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

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

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

123
        assert(ret);
14,097✔
124

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

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

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

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

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

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

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

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

154
                        return errno_or_else(EIO);
×
155
                }
156

157
                if (ul > PID_T_MAX)
3,880✔
158
                        return -EIO;
159

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

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

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

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

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

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

188
                if (pid == 0)
1,959✔
189
                        return -EREMOTE;
190

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

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

201
bool cg_kill_supported(void) {
×
202
        static thread_local int supported = -1;
×
203

204
        if (supported >= 0)
×
205
                return supported;
×
206

207
        if (cg_all_unified() <= 0)
×
208
                return (supported = false);
×
209

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

217
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret) {
13,698✔
218
        _cleanup_free_ char *fs = NULL;
13,698✔
219
        DIR *d;
13,698✔
220
        int r;
13,698✔
221

222
        assert(ret);
13,698✔
223

224
        /* This is not recursive! */
225

226
        r = cg_get_path(controller, path, NULL, &fs);
13,698✔
227
        if (r < 0)
13,698✔
228
                return r;
229

230
        d = opendir(fs);
13,698✔
231
        if (!d)
13,698✔
232
                return -errno;
8,365✔
233

234
        *ret = d;
5,333✔
235
        return 0;
5,333✔
236
}
237

238
int cg_read_subgroup(DIR *d, char **ret) {
6,277✔
239
        assert(d);
6,277✔
240
        assert(ret);
6,277✔
241

242
        FOREACH_DIRENT_ALL(de, d, return -errno) {
246,820✔
243
                if (de->d_type != DT_DIR)
241,430✔
244
                        continue;
229,763✔
245

246
                if (dot_or_dot_dot(de->d_name))
11,667✔
247
                        continue;
10,780✔
248

249
                return strdup_to_full(ret, de->d_name);
887✔
250
        }
251

252
        *ret = NULL;
5,390✔
253
        return 0;
5,390✔
254
}
255

256
int cg_kill(
13,595✔
257
                const char *path,
258
                int sig,
259
                CGroupFlags flags,
260
                Set *killed_pids,
261
                cg_kill_log_func_t log_kill,
262
                void *userdata) {
263

264
        _cleanup_set_free_ Set *allocated_set = NULL;
13,595✔
265
        int r, ret = 0;
13,595✔
266

267
        assert(path);
13,595✔
268
        assert(sig >= 0);
13,595✔
269

270
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
271
          * don't send SIGCONT on SIGKILL. */
272
        if (IN_SET(sig, SIGCONT, SIGKILL))
13,595✔
273
                flags &= ~CGROUP_SIGCONT;
2,096✔
274

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

280
        if (!killed_pids) {
13,595✔
281
                killed_pids = allocated_set = set_new(NULL);
639✔
282
                if (!killed_pids)
639✔
283
                        return -ENOMEM;
284
        }
285

286
        bool done;
13,691✔
287
        do {
13,691✔
288
                _cleanup_fclose_ FILE *f = NULL;
8,365✔
289
                int ret_log_kill;
13,691✔
290

291
                done = true;
13,691✔
292

293
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f);
13,691✔
294
                if (r == -ENOENT)
13,691✔
295
                        break;
296
                if (r < 0)
5,326✔
297
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
298

299
                for (;;) {
7,184✔
300
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
7,184✔
301

302
                        r = cg_read_pidref(f, &pidref, flags);
7,184✔
303
                        if (r < 0)
7,184✔
304
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
305
                        if (r == 0)
7,184✔
306
                                break;
307

308
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
1,858✔
309
                                continue;
639✔
310

311
                        if (set_contains(killed_pids, PID_TO_PTR(pidref.pid)))
1,219✔
312
                                continue;
877✔
313

314
                        /* Ignore kernel threads to mimic the behavior of cgroup.kill. */
315
                        if (pidref_is_kernel_thread(&pidref) > 0) {
342✔
316
                                log_debug("Ignoring kernel thread with pid " PID_FMT " in cgroup '%s'", pidref.pid, path);
×
317
                                continue;
×
318
                        }
319

320
                        if (log_kill)
342✔
321
                                ret_log_kill = log_kill(&pidref, sig, userdata);
94✔
322

323
                        /* If we haven't killed this process yet, kill it */
324
                        r = pidref_kill(&pidref, sig);
342✔
325
                        if (r < 0 && r != -ESRCH)
342✔
326
                                RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path));
×
327
                        if (r >= 0) {
342✔
328
                                if (flags & CGROUP_SIGCONT)
342✔
329
                                        (void) pidref_kill(&pidref, SIGCONT);
247✔
330

331
                                if (ret == 0) {
342✔
332
                                        if (log_kill)
169✔
333
                                                ret = ret_log_kill;
334
                                        else
335
                                                ret = 1;
75✔
336
                                }
337
                        }
338

339
                        done = false;
342✔
340

341
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
342✔
342
                        if (r < 0)
342✔
343
                                return RET_GATHER(ret, r);
×
344
                }
345

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

349
        } while (!done);
5,326✔
350

351
        return ret;
352
}
353

354
int cg_kill_recursive(
12,954✔
355
                const char *path,
356
                int sig,
357
                CGroupFlags flags,
358
                Set *killed_pids,
359
                cg_kill_log_func_t log_kill,
360
                void *userdata) {
361

362
        _cleanup_set_free_ Set *allocated_set = NULL;
×
363
        _cleanup_closedir_ DIR *d = NULL;
12,954✔
364
        int r, ret;
12,954✔
365

366
        assert(path);
12,954✔
367
        assert(sig >= 0);
12,954✔
368

369
        if (!killed_pids) {
12,954✔
370
                killed_pids = allocated_set = set_new(NULL);
12,334✔
371
                if (!killed_pids)
12,334✔
372
                        return -ENOMEM;
373
        }
374

375
        ret = cg_kill(path, sig, flags, killed_pids, log_kill, userdata);
12,954✔
376

377
        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d);
12,954✔
378
        if (r < 0) {
12,954✔
379
                if (r != -ENOENT)
8,365✔
380
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
381

382
                return ret;
8,365✔
383
        }
384

385
        for (;;) {
4,721✔
386
                _cleanup_free_ char *fn = NULL, *p = NULL;
4,655✔
387

388
                r = cg_read_subgroup(d, &fn);
4,655✔
389
                if (r < 0) {
4,655✔
390
                        RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path));
×
391
                        break;
392
                }
393
                if (r == 0)
4,655✔
394
                        break;
395

396
                p = path_join(empty_to_root(path), fn);
66✔
397
                if (!p)
66✔
398
                        return -ENOMEM;
×
399

400
                r = cg_kill_recursive(p, sig, flags, killed_pids, log_kill, userdata);
66✔
401
                if (r < 0)
66✔
402
                        log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p);
×
403
                if (r != 0 && ret >= 0)
66✔
404
                        ret = r;
15✔
405
        }
406

407
        return ret;
4,589✔
408
}
409

410
int cg_kill_kernel_sigkill(const char *path) {
×
411
        _cleanup_free_ char *killfile = NULL;
×
412
        int r;
×
413

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

417
        assert(path);
×
418

419
        if (!cg_kill_supported())
×
420
                return -EOPNOTSUPP;
421

422
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.kill", &killfile);
×
423
        if (r < 0)
×
424
                return r;
425

426
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
×
427
        if (r < 0)
×
428
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
429

430
        return 0;
431
}
432

433
static const char *controller_to_dirname(const char *controller) {
×
434
        assert(controller);
×
435

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

440
        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
×
441
                if (cg_hybrid_unified() > 0)
×
442
                        controller = SYSTEMD_CGROUP_CONTROLLER_HYBRID;
443
                else
444
                        controller = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
×
445
        }
446

447
        return startswith(controller, "name=") ?: controller;
×
448
}
449

450
static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **ret) {
×
451
        const char *dn;
×
452
        char *t = NULL;
×
453

454
        assert(ret);
×
455
        assert(controller);
×
456

457
        dn = controller_to_dirname(controller);
×
458

459
        if (isempty(path) && isempty(suffix))
×
460
                t = path_join("/sys/fs/cgroup", dn);
×
461
        else if (isempty(path))
×
462
                t = path_join("/sys/fs/cgroup", dn, suffix);
×
463
        else if (isempty(suffix))
×
464
                t = path_join("/sys/fs/cgroup", dn, path);
×
465
        else
466
                t = path_join("/sys/fs/cgroup", dn, path, suffix);
×
467
        if (!t)
×
468
                return -ENOMEM;
469

470
        *ret = t;
×
471
        return 0;
×
472
}
473

474
static int join_path_unified(const char *path, const char *suffix, char **ret) {
212,637✔
475
        char *t;
212,637✔
476

477
        assert(ret);
212,637✔
478

479
        if (isempty(path) && isempty(suffix))
227,625✔
480
                t = strdup("/sys/fs/cgroup");
1,105✔
481
        else if (isempty(path))
211,532✔
482
                t = path_join("/sys/fs/cgroup", suffix);
13,883✔
483
        else if (isempty(suffix))
197,649✔
484
                t = path_join("/sys/fs/cgroup", path);
73,736✔
485
        else
486
                t = path_join("/sys/fs/cgroup", path, suffix);
123,913✔
487
        if (!t)
212,637✔
488
                return -ENOMEM;
489

490
        *ret = t;
212,637✔
491
        return 0;
212,637✔
492
}
493

494
int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret) {
212,637✔
495
        int r;
212,637✔
496

497
        assert(ret);
212,637✔
498

499
        if (!controller) {
212,637✔
500
                char *t;
×
501

502
                /* If no controller is specified, we return the path *below* the controllers, without any
503
                 * prefix. */
504

505
                if (isempty(path) && isempty(suffix))
×
506
                        return -EINVAL;
507

508
                if (isempty(suffix))
×
509
                        t = strdup(path);
×
510
                else if (isempty(path))
×
511
                        t = strdup(suffix);
×
512
                else
513
                        t = path_join(path, suffix);
×
514
                if (!t)
×
515
                        return -ENOMEM;
516

517
                *ret = path_simplify(t);
×
518
                return 0;
×
519
        }
520

521
        if (!cg_controller_is_valid(controller))
212,637✔
522
                return -EINVAL;
523

524
        r = cg_all_unified();
212,637✔
525
        if (r < 0)
212,637✔
526
                return r;
527
        if (r > 0)
212,637✔
528
                r = join_path_unified(path, suffix, ret);
212,637✔
529
        else
530
                r = join_path_legacy(controller, path, suffix, ret);
×
531
        if (r < 0)
212,637✔
532
                return r;
533

534
        path_simplify(*ret);
212,637✔
535
        return 0;
212,637✔
536
}
537

538
static int controller_is_v1_accessible(const char *root, const char *controller) {
×
539
        const char *cpath, *dn;
×
540

541
        assert(controller);
×
542

543
        dn = controller_to_dirname(controller);
×
544

545
        /* If root if specified, we check that:
546
         * - possible subcgroup is created at root,
547
         * - we can modify the hierarchy. */
548

549
        cpath = strjoina("/sys/fs/cgroup/", dn, root, root ? "/cgroup.procs" : NULL);
×
550
        return access_nofollow(cpath, root ? W_OK : F_OK);
×
551
}
552

553
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret) {
19,145✔
554
        int r;
19,145✔
555

556
        assert(controller);
19,145✔
557
        assert(ret);
19,145✔
558

559
        if (!cg_controller_is_valid(controller))
19,145✔
560
                return -EINVAL;
561

562
        r = cg_all_unified();
19,145✔
563
        if (r < 0)
19,145✔
564
                return r;
565
        if (r > 0) {
19,145✔
566
                /* In the unified hierarchy all controllers are considered accessible,
567
                 * except for the named hierarchies */
568
                if (startswith(controller, "name="))
19,145✔
569
                        return -EOPNOTSUPP;
570
        } else {
571
                /* Check if the specified controller is actually accessible */
572
                r = controller_is_v1_accessible(NULL, controller);
×
573
                if (r < 0)
×
574
                        return r;
575
        }
576

577
        return cg_get_path(controller, path, suffix, ret);
19,145✔
578
}
579

580
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
4,476✔
581
        _cleanup_free_ char *fs = NULL;
4,476✔
582
        int r;
4,476✔
583

584
        assert(path);
4,476✔
585
        assert(name);
4,476✔
586
        assert(value || size <= 0);
4,476✔
587

588
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
4,476✔
589
        if (r < 0)
4,476✔
590
                return r;
591

592
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
4,476✔
593
}
594

595
int cg_get_xattr(const char *path, const char *name, char **ret, size_t *ret_size) {
15,716✔
596
        _cleanup_free_ char *fs = NULL;
15,716✔
597
        int r;
15,716✔
598

599
        assert(path);
15,716✔
600
        assert(name);
15,716✔
601

602
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
15,716✔
603
        if (r < 0)
15,716✔
604
                return r;
605

606
        return lgetxattr_malloc(fs, name, ret, ret_size);
15,716✔
607
}
608

609
int cg_get_xattr_bool(const char *path, const char *name) {
147✔
610
        _cleanup_free_ char *fs = NULL;
147✔
611
        int r;
147✔
612

613
        assert(path);
147✔
614
        assert(name);
147✔
615

616
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
147✔
617
        if (r < 0)
147✔
618
                return r;
619

620
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
147✔
621
}
622

623
int cg_remove_xattr(const char *path, const char *name) {
24,868✔
624
        _cleanup_free_ char *fs = NULL;
24,868✔
625
        int r;
24,868✔
626

627
        assert(path);
24,868✔
628
        assert(name);
24,868✔
629

630
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
24,868✔
631
        if (r < 0)
24,868✔
632
                return r;
633

634
        return RET_NERRNO(removexattr(fs, name));
49,736✔
635
}
636

637
int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
41,904✔
638
        _cleanup_fclose_ FILE *f = NULL;
41,904✔
639
        const char *fs, *controller_str = NULL;  /* avoid false maybe-uninitialized warning */
41,904✔
640
        int unified, r;
41,904✔
641

642
        assert(pid >= 0);
41,904✔
643
        assert(ret_path);
41,904✔
644

645
        if (controller) {
41,904✔
646
                if (!cg_controller_is_valid(controller))
41,721✔
647
                        return -EINVAL;
648
        } else
649
                controller = SYSTEMD_CGROUP_CONTROLLER;
650

651
        unified = cg_unified_controller(controller);
41,904✔
652
        if (unified < 0)
41,904✔
653
                return unified;
654
        if (unified == 0) {
41,904✔
655
                if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
×
656
                        controller_str = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
657
                else
658
                        controller_str = controller;
×
659
        }
660

661
        fs = procfs_file_alloca(pid, "cgroup");
48,108✔
662
        r = fopen_unlocked(fs, "re", &f);
41,904✔
663
        if (r == -ENOENT)
41,904✔
664
                return -ESRCH;
665
        if (r < 0)
37,734✔
666
                return r;
667

668
        for (;;) {
37,733✔
669
                _cleanup_free_ char *line = NULL;
37,733✔
670
                char *e;
37,733✔
671

672
                r = read_line(f, LONG_LINE_MAX, &line);
37,733✔
673
                if (r < 0)
37,733✔
674
                        return r;
675
                if (r == 0)
37,721✔
676
                        return -ENODATA;
677

678
                if (unified) {
37,721✔
679
                        e = startswith(line, "0:");
37,721✔
680
                        if (!e)
37,721✔
681
                                continue;
×
682

683
                        e = strchr(e, ':');
37,721✔
684
                        if (!e)
37,721✔
685
                                continue;
×
686
                } else {
687
                        char *l;
×
688

689
                        l = strchr(line, ':');
×
690
                        if (!l)
×
691
                                continue;
×
692

693
                        l++;
×
694
                        e = strchr(l, ':');
×
695
                        if (!e)
×
696
                                continue;
×
697
                        *e = 0;
×
698

699
                        assert(controller_str);
×
700
                        r = string_contains_word(l, ",", controller_str);
×
701
                        if (r < 0)
×
702
                                return r;
703
                        if (r == 0)
×
704
                                continue;
×
705
                }
706

707
                _cleanup_free_ char *path = strdup(e + 1);
37,721✔
708
                if (!path)
37,721✔
709
                        return -ENOMEM;
710

711
                /* Refuse cgroup paths from outside our cgroup namespace */
712
                if (startswith(path, "/../"))
37,721✔
713
                        return -EUNATCH;
714

715
                /* Truncate suffix indicating the process is a zombie */
716
                e = endswith(path, " (deleted)");
37,721✔
717
                if (e)
37,721✔
718
                        *e = 0;
261✔
719

720
                *ret_path = TAKE_PTR(path);
37,721✔
721
                return 0;
37,721✔
722
        }
723
}
724

725
int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_path) {
11,005✔
726
        _cleanup_free_ char *path = NULL;
11,005✔
727
        int r;
11,005✔
728

729
        assert(ret_path);
11,005✔
730

731
        if (!pidref_is_set(pidref))
11,005✔
732
                return -ESRCH;
733
        if (pidref_is_remote(pidref))
22,010✔
734
                return -EREMOTE;
735

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

740
        r = cg_pid_get_path(controller, pidref->pid, &path);
11,005✔
741
        if (r < 0)
11,005✔
742
                return r;
743

744
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
745
        r = pidref_verify(pidref);
11,003✔
746
        if (r < 0)
11,003✔
747
                return r;
748

749
        *ret_path = TAKE_PTR(path);
11,003✔
750
        return 0;
11,003✔
751
}
752

753
int cg_is_empty(const char *controller, const char *path) {
2,279✔
754
        _cleanup_free_ char *t = NULL;
2,279✔
755
        int r;
2,279✔
756

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

760
        assert(path);
2,279✔
761

762
        /* The root cgroup is always populated */
763
        if (empty_or_root(path))
2,279✔
764
                return false;
765

766
        r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.events", STRV_MAKE("populated"), &t);
2,279✔
767
        if (r == -ENOENT)
2,279✔
768
                return true;
769
        if (r < 0)
4✔
770
                return r;
771

772
        return streq(t, "0");
4✔
773
}
774

775
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
23✔
776
        _cleanup_free_ char *controller = NULL, *path = NULL;
23✔
777
        int r;
23✔
778

779
        assert(spec);
23✔
780

781
        if (*spec == '/') {
23✔
782
                if (!path_is_normalized(spec))
15✔
783
                        return -EINVAL;
784

785
                if (ret_path) {
15✔
786
                        r = path_simplify_alloc(spec, &path);
15✔
787
                        if (r < 0)
15✔
788
                                return r;
789
                }
790

791
        } else {
792
                const char *e;
8✔
793

794
                e = strchr(spec, ':');
8✔
795
                if (e) {
8✔
796
                        controller = strndup(spec, e-spec);
6✔
797
                        if (!controller)
6✔
798
                                return -ENOMEM;
799
                        if (!cg_controller_is_valid(controller))
6✔
800
                                return -EINVAL;
801

802
                        if (!isempty(e + 1)) {
3✔
803
                                path = strdup(e+1);
2✔
804
                                if (!path)
2✔
805
                                        return -ENOMEM;
806

807
                                if (!path_is_normalized(path) ||
2✔
808
                                    !path_is_absolute(path))
2✔
809
                                        return -EINVAL;
810

811
                                path_simplify(path);
1✔
812
                        }
813

814
                } else {
815
                        if (!cg_controller_is_valid(spec))
2✔
816
                                return -EINVAL;
817

818
                        if (ret_controller) {
1✔
819
                                controller = strdup(spec);
1✔
820
                                if (!controller)
1✔
821
                                        return -ENOMEM;
822
                        }
823
                }
824
        }
825

826
        if (ret_controller)
18✔
827
                *ret_controller = TAKE_PTR(controller);
18✔
828
        if (ret_path)
18✔
829
                *ret_path = TAKE_PTR(path);
18✔
830
        return 0;
831
}
832

833
int cg_mangle_path(const char *path, char **ret) {
119✔
834
        _cleanup_free_ char *c = NULL, *p = NULL;
119✔
835
        int r;
119✔
836

837
        assert(path);
119✔
838
        assert(ret);
119✔
839

840
        /* First, check if it already is a filesystem path */
841
        if (path_startswith(path, "/sys/fs/cgroup"))
119✔
842
                return path_simplify_alloc(path, ret);
115✔
843

844
        /* Otherwise, treat it as cg spec */
845
        r = cg_split_spec(path, &c, &p);
4✔
846
        if (r < 0)
4✔
847
                return r;
848

849
        return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, ret);
8✔
850
}
851

852
int cg_get_root_path(char **ret_path) {
14,369✔
853
        char *p, *e;
14,369✔
854
        int r;
14,369✔
855

856
        assert(ret_path);
14,369✔
857

858
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
14,369✔
859
        if (r < 0)
14,369✔
860
                return r;
14,369✔
861

862
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
14,369✔
863
        if (e)
14,369✔
864
                *e = 0;
14,339✔
865

866
        *ret_path = p;
14,369✔
867
        return 0;
14,369✔
868
}
869

870
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
11,015✔
871
        int r;
11,015✔
872

873
        assert(cgroup);
11,015✔
874
        assert(ret_shifted);
11,015✔
875

876
        _cleanup_free_ char *rt = NULL;
11,015✔
877
        if (!root) {
11,015✔
878
                /* If the root was specified let's use that, otherwise
879
                 * let's determine it from PID 1 */
880

881
                r = cg_get_root_path(&rt);
1,848✔
882
                if (r < 0)
1,848✔
883
                        return r;
884

885
                root = rt;
1,848✔
886
        }
887

888
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
11,015✔
889
        return 0;
11,015✔
890
}
891

892
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
15,020✔
893
        _cleanup_free_ char *raw = NULL;
15,020✔
894
        const char *c;
15,020✔
895
        int r;
15,020✔
896

897
        assert(pid >= 0);
15,020✔
898
        assert(ret_cgroup);
15,020✔
899

900
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw);
15,020✔
901
        if (r < 0)
15,020✔
902
                return r;
903

904
        r = cg_shift_path(raw, root, &c);
10,839✔
905
        if (r < 0)
10,839✔
906
                return r;
907

908
        if (c == raw) {
10,839✔
909
                *ret_cgroup = TAKE_PTR(raw);
10,839✔
910
                return 0;
10,839✔
911
        }
912

913
        return strdup_to(ret_cgroup, c);
×
914
}
915

916
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
32,678✔
917
        assert(cgroup);
32,678✔
918

919
        size_t n = strcspn(cgroup, "/");
32,678✔
920
        if (n < 3)
32,678✔
921
                return -ENXIO;
922

923
        char *c = strndupa_safe(cgroup, n);
32,659✔
924
        c = cg_unescape(c);
32,659✔
925

926
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
32,659✔
927
                return -ENXIO;
928

929
        if (ret_unit)
32,651✔
930
                return strdup_to(ret_unit, c);
32,651✔
931

932
        return 0;
933
}
934

935
static bool valid_slice_name(const char *p, size_t n) {
116,045✔
936

937
        if (!p)
116,045✔
938
                return false;
939

940
        if (n < STRLEN("x.slice"))
116,018✔
941
                return false;
942

943
        if (memcmp(p + n - 6, ".slice", 6) == 0) {
115,988✔
944
                char buf[n+1], *c;
58,791✔
945

946
                memcpy(buf, p, n);
58,791✔
947
                buf[n] = 0;
58,791✔
948

949
                c = cg_unescape(buf);
58,791✔
950

951
                return unit_name_is_valid(c, UNIT_NAME_PLAIN);
58,791✔
952
        }
953

954
        return false;
955
}
956

957
static const char *skip_slices(const char *p) {
41,417✔
958
        assert(p);
41,417✔
959

960
        /* Skips over all slice assignments */
961

962
        for (;;) {
126,661✔
963
                size_t n;
84,039✔
964

965
                p += strspn(p, "/");
84,039✔
966

967
                n = strcspn(p, "/");
84,039✔
968
                if (!valid_slice_name(p, n))
84,039✔
969
                        return p;
41,417✔
970

971
                p += n;
42,622✔
972
        }
973
}
974

975
int cg_path_get_unit(const char *path, char **ret) {
17,068✔
976
        _cleanup_free_ char *unit = NULL;
17,068✔
977
        const char *e;
17,068✔
978
        int r;
17,068✔
979

980
        assert(path);
17,068✔
981

982
        e = skip_slices(path);
17,068✔
983

984
        r = cg_path_decode_unit(e, &unit);
17,068✔
985
        if (r < 0)
17,068✔
986
                return r;
987

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

992
        if (ret)
17,045✔
993
                *ret = TAKE_PTR(unit);
17,045✔
994
        return 0;
995
}
996

997
int cg_path_get_unit_path(const char *path, char **ret) {
9,007✔
998
        _cleanup_free_ char *path_copy = NULL;
9,007✔
999
        char *unit_name;
9,007✔
1000

1001
        assert(path);
9,007✔
1002
        assert(ret);
9,007✔
1003

1004
        path_copy = strdup(path);
9,007✔
1005
        if (!path_copy)
9,007✔
1006
                return -ENOMEM;
1007

1008
        unit_name = (char *)skip_slices(path_copy);
9,007✔
1009
        unit_name[strcspn(unit_name, "/")] = 0;
9,007✔
1010

1011
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,007✔
1012
                return -ENXIO;
1013

1014
        *ret = TAKE_PTR(path_copy);
9,004✔
1015

1016
        return 0;
9,004✔
1017
}
1018

1019
int cg_pid_get_unit(pid_t pid, char **ret_unit) {
626✔
1020
        _cleanup_free_ char *cgroup = NULL;
626✔
1021
        int r;
626✔
1022

1023
        assert(ret_unit);
626✔
1024

1025
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
626✔
1026
        if (r < 0)
626✔
1027
                return r;
1028

1029
        return cg_path_get_unit(cgroup, ret_unit);
622✔
1030
}
1031

1032
int cg_pidref_get_unit(const PidRef *pidref, char **ret) {
550✔
1033
        _cleanup_free_ char *unit = NULL;
550✔
1034
        int r;
550✔
1035

1036
        assert(ret);
550✔
1037

1038
        if (!pidref_is_set(pidref))
550✔
1039
                return -ESRCH;
1040
        if (pidref_is_remote(pidref))
1,100✔
1041
                return -EREMOTE;
1042

1043
        r = cg_pid_get_unit(pidref->pid, &unit);
550✔
1044
        if (r < 0)
550✔
1045
                return r;
1046

1047
        r = pidref_verify(pidref);
546✔
1048
        if (r < 0)
546✔
1049
                return r;
1050

1051
        *ret = TAKE_PTR(unit);
546✔
1052
        return 0;
546✔
1053
}
1054

1055
/**
1056
 * Skip session-*.scope, but require it to be there.
1057
 */
1058
static const char *skip_session(const char *p) {
14,917✔
1059
        size_t n;
14,917✔
1060

1061
        if (isempty(p))
14,917✔
1062
                return NULL;
1063

1064
        p += strspn(p, "/");
14,913✔
1065

1066
        n = strcspn(p, "/");
14,913✔
1067
        if (n < STRLEN("session-x.scope"))
14,913✔
1068
                return NULL;
1069

1070
        if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) {
14,751✔
1071
                char buf[n - 8 - 6 + 1];
23✔
1072

1073
                memcpy(buf, p + 8, n - 8 - 6);
23✔
1074
                buf[n - 8 - 6] = 0;
23✔
1075

1076
                /* Note that session scopes never need unescaping,
1077
                 * since they cannot conflict with the kernel's own
1078
                 * names, hence we don't need to call cg_unescape()
1079
                 * here. */
1080

1081
                if (!session_id_valid(buf))
23✔
1082
                        return NULL;
23✔
1083

1084
                p += n;
23✔
1085
                p += strspn(p, "/");
23✔
1086
                return p;
23✔
1087
        }
1088

1089
        return NULL;
1090
}
1091

1092
/**
1093
 * Skip user@*.service or capsule@*.service, but require either of them to be there.
1094
 */
1095
static const char *skip_user_manager(const char *p) {
15,342✔
1096
        size_t n;
15,342✔
1097

1098
        if (isempty(p))
15,342✔
1099
                return NULL;
15,342✔
1100

1101
        p += strspn(p, "/");
15,338✔
1102

1103
        n = strcspn(p, "/");
15,338✔
1104
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,338✔
1105
                return NULL;
1106

1107
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
1108
         * resolve user/capsule name. */
1109
        _cleanup_free_ char *unit_name = strndup(p, n);
15,176✔
1110
        if (!unit_name)
15,176✔
1111
                return NULL;
1112

1113
        _cleanup_free_ char *i = NULL;
15,176✔
1114
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
15,176✔
1115

1116
        if (type != UNIT_NAME_INSTANCE)
15,176✔
1117
                return NULL;
1118

1119
        /* Note that user manager services never need unescaping, since they cannot conflict with the
1120
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
1121
         * instance names, they should be always valid as we validate them upon unit start. */
1122
        if (startswith(unit_name, "user@")) {
509✔
1123
                if (parse_uid(i, NULL) < 0)
420✔
1124
                        return NULL;
1125

1126
                p += n;
420✔
1127
                p += strspn(p, "/");
420✔
1128
                return p;
420✔
1129
        } else if (startswith(unit_name, "capsule@")) {
89✔
1130
                if (capsule_name_is_valid(i) <= 0)
5✔
1131
                        return NULL;
1132

1133
                p += n;
5✔
1134
                p += strspn(p, "/");
5✔
1135
                return p;
5✔
1136
        }
1137

1138
        return NULL;
1139
}
1140

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

1144
        assert(path);
15,342✔
1145

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

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

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

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

1161
        assert(path);
7,698✔
1162

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

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

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

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

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

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

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

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

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

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

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

1212
        assert(path);
8,482✔
1213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1273
        assert(path);
7,923✔
1274

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

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

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

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

1291
        return 0;
1292
}
1293

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

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

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

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

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

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

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

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

1325
        return 0;
1326
}
1327

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

1331
        assert(p);
15,837✔
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 (;;) {
48,175✔
1337
                const char *s;
32,006✔
1338
                int n;
32,006✔
1339

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

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

1349
        if (e)
15,837✔
1350
                return cg_path_decode_unit(e, ret_slice);
15,601✔
1351

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

1355
        return 0;
1356
}
1357

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

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

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

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

1373
        t = skip_user_prefix(p);
7,644✔
1374
        if (!t)
7,644✔
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);
218✔
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) {
20,583✔
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))
20,583✔
1401
                return true;
1402

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

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

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

1412
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
287,966✔
1413
                const char *q;
267,397✔
1414

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

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

1423
        return false;
1424
}
1425

1426
int cg_escape(const char *p, char **ret) {
20,330✔
1427
        _cleanup_free_ char *n = NULL;
20,330✔
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)) {
20,330✔
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);
20,323✔
1445
                if (!n)
20,323✔
1446
                        return -ENOMEM;
1447
        }
1448

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

1453
char* cg_unescape(const char *p) {
100,507✔
1454
        assert(p);
100,507✔
1455

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

1459
        if (p[0] == '_')
100,507✔
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) {
273,521✔
1470
        const char *t, *s;
273,521✔
1471

1472
        if (!p)
273,521✔
1473
                return false;
1474

1475
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
273,521✔
1476
                return true;
1477

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

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

1485
        for (t = p; *t; t++)
575,569✔
1486
                if (!strchr(CONTROLLER_VALID, *t))
487,374✔
1487
                        return false;
1488

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

1492
        return true;
1493
}
1494

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

1500
        assert(unit);
8,756✔
1501
        assert(ret);
8,756✔
1502

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

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

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

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

1516
        dash = strchr(p, '-');
8,737✔
1517

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

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

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

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

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

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

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

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

1559
        *ret = TAKE_PTR(s);
8,732✔
1560
        return 0;
8,732✔
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) {
34,524✔
1588
        _cleanup_free_ char *p = NULL;
34,524✔
1589
        int r;
34,524✔
1590

1591
        assert(attribute);
34,524✔
1592

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

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

1609
        assert(attribute);
30,424✔
1610

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

1615
        return read_one_line_file(p, ret);
30,424✔
1616
}
1617

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

1623
        assert(ret);
25,506✔
1624

1625
        r = cg_get_attribute(controller, path, attribute, &value);
25,506✔
1626
        if (r == -ENOENT)
25,506✔
1627
                return -ENODATA;
1628
        if (r < 0)
23,846✔
1629
                return r;
1630

1631
        if (streq(value, "max")) {
23,846✔
1632
                *ret = CGROUP_LIMIT_MAX;
5,726✔
1633
                return 0;
5,726✔
1634
        }
1635

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

1640
        *ret = v;
18,120✔
1641
        return 0;
18,120✔
1642
}
1643

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

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

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

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

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

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

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

1720
                for (i = 0; i < n; i++) {
119,679✔
1721
                        w = first_word(p, keys[i]);
77,071✔
1722
                        if (w)
77,071✔
1723
                                break;
1724
                }
1725

1726
                if (w) {
70,042✔
1727
                        if (v[i]) { /* duplicate entry? */
27,434✔
UNCOV
1728
                                r = -EBADMSG;
×
UNCOV
1729
                                goto fail;
×
1730
                        }
1731

1732
                        size_t l = strcspn(w, NEWLINE);
27,434✔
1733

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

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

1744
                        p = w + l;
7,029✔
1745
                } else
1746
                        p += strcspn(p, NEWLINE);
42,608✔
1747

1748
                p += strspn(p, NEWLINE);
49,637✔
1749
        }
1750

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

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

UNCOV
1759
fail:
×
1760
        free_many_charp(v, n);
22,734✔
1761
        return r;
1762
}
1763

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

1770
        assert(ret);
10,234✔
1771

1772
        if (mask == 0) {
10,234✔
1773
                *ret = NULL;
4,963✔
1774
                return 0;
4,963✔
1775
        }
1776

1777
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
73,794✔
1778
                const char *k;
68,523✔
1779
                size_t l;
68,523✔
1780

1781
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
68,523✔
1782
                        continue;
32,718✔
1783

1784
                k = cgroup_controller_to_string(c);
35,805✔
1785
                l = strlen(k);
35,805✔
1786

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

1790
                if (space)
35,805✔
1791
                        s[n] = ' ';
30,534✔
1792
                memcpy(s + n + space, k, l);
35,805✔
1793
                n += space + l;
35,805✔
1794

1795
                space = true;
35,805✔
1796
        }
1797

1798
        assert(s);
5,271✔
1799

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

1803
        return 0;
5,271✔
1804
}
1805

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

1809
        assert(ret);
5,129✔
1810
        assert(value);
5,129✔
1811

1812
        for (;;) {
39,324✔
1813
                _cleanup_free_ char *n = NULL;
34,195✔
1814
                CGroupController v;
39,324✔
1815
                int r;
39,324✔
1816

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

1823
                v = cgroup_controller_from_string(n);
34,195✔
1824
                if (v < 0)
34,195✔
1825
                        continue;
642✔
1826

1827
                m |= CGROUP_CONTROLLER_TO_MASK(v);
33,553✔
1828
        }
1829

1830
        *ret = m;
5,129✔
1831
        return 0;
5,129✔
1832
}
1833

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

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

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

1863
                /* Mask controllers that are not supported in unified hierarchy. */
1864
                mask &= CGROUP_MASK_V2;
464✔
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++) {
×
UNCOV
1873
                        CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
×
1874
                        const char *n;
×
1875

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

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

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

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

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

1897
        return cg_mask_supported_subtree(root, ret);
218✔
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) {
279,635✔
1913
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
279,635✔
1914

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

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

1930
        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
12,908✔
1931
                log_debug("Found cgroup2 on /sys/fs/cgroup/, full unified hierarchy");
12,908✔
1932
                unified_cache = CGROUP_UNIFIED_ALL;
12,908✔
1933
        } else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) {
×
UNCOV
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");
×
UNCOV
1937
                        unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
1938
                        unified_systemd_v232 = false;
×
1939
                } else {
UNCOV
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. */
UNCOV
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)");
×
UNCOV
1951
                                unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
1952
                                unified_systemd_v232 = true;
×
UNCOV
1953
                        } else if (F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) {
×
1954
                                log_debug("Found cgroup on /sys/fs/cgroup/systemd, legacy hierarchy");
×
UNCOV
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);
UNCOV
1959
                                unified_cache = CGROUP_UNIFIED_NONE;
×
1960
                        }
1961
                }
UNCOV
1962
        } else if (F_TYPE_EQUAL(fs.f_type, SYSFS_MAGIC)) {
×
UNCOV
1963
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
1964
                                       "No filesystem is currently mounted on /sys/fs/cgroup.");
1965
        } else
UNCOV
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;
12,908✔
1971
}
1972

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

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

1980
        if (r == CGROUP_UNIFIED_NONE)
41,905✔
1981
                return false;
1982

1983
        if (r >= CGROUP_UNIFIED_ALL)
41,905✔
1984
                return true;
1985

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

1989
int cg_all_unified(void) {
237,724✔
1990
        int r;
237,724✔
1991

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

1996
        return r >= CGROUP_UNIFIED_ALL;
237,724✔
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) {
41✔
2026
        int r;
41✔
2027

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

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

2034
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
39✔
2035
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
39✔
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✔
UNCOV
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);
5,473✔
2065

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

2082
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
358,697✔
2083

2084
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
2085
        [MANAGED_OOM_AUTO] = "auto",
2086
        [MANAGED_OOM_KILL] = "kill",
2087
};
2088

2089
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
31,971✔
2090

2091
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
2092
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
2093
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
2094
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
2095
};
2096

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