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

systemd / systemd / 15288324789

27 May 2025 07:40PM UTC coverage: 71.981% (-0.07%) from 72.046%
15288324789

push

github

yuwata
timedate: print better errors when systemd-timesyncd.service unavailable

If the error is a common bus error indicating the service is not
available, print a more user-friendly message indicating so.

0 of 7 new or added lines in 1 file covered. (0.0%)

3467 existing lines in 62 files now uncovered.

299170 of 415625 relevant lines covered (71.98%)

704053.27 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

70
                cgroupfs_fd = fsfd;
71
        }
72

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

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

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,075✔
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) {
18,243✔
119
        _cleanup_free_ char *fs = NULL;
18,243✔
120
        FILE *f;
18,243✔
121
        int r;
18,243✔
122

123
        assert(ret);
18,243✔
124

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

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

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

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

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

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

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

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

154
                        return errno_or_else(EIO);
×
155
                }
156

157
                if (ul > PID_T_MAX)
3,975✔
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,975✔
164
                        continue;
×
165

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

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

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

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

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

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

191
                r = pidref_set_pid(ret, pid);
2,060✔
192
                if (r >= 0)
2,060✔
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

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

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

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

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

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

222
        assert(ret);
17,847✔
223

224
        /* This is not recursive! */
225

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

230
        d = opendir(fs);
17,847✔
231
        if (!d)
17,847✔
232
                return -errno;
12,582✔
233

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

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

242
        FOREACH_DIRENT_ALL(de, d, return -errno) {
243,766✔
243
                if (de->d_type != DT_DIR)
238,443✔
244
                        continue;
226,895✔
245

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

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

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

256
int cg_kill(
17,728✔
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;
17,728✔
265
        int r, ret = 0;
17,728✔
266

267
        assert(path);
17,728✔
268
        assert(sig >= 0);
17,728✔
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))
17,728✔
273
                flags &= ~CGROUP_SIGCONT;
4,190✔
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) {
17,728✔
281
                killed_pids = allocated_set = set_new(NULL);
624✔
282
                if (!killed_pids)
624✔
283
                        return -ENOMEM;
284
        }
285

286
        bool done;
17,830✔
287
        do {
17,830✔
288
                _cleanup_fclose_ FILE *f = NULL;
12,582✔
289
                int ret_log_kill;
17,830✔
290

291
                done = true;
17,830✔
292

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

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

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

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

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

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

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

323
                        /* If we haven't killed this process yet, kill it */
324
                        r = pidref_kill(&pidref, sig);
373✔
325
                        if (r < 0 && r != -ESRCH)
373✔
UNCOV
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) {
373✔
328
                                if (flags & CGROUP_SIGCONT)
369✔
329
                                        (void) pidref_kill(&pidref, SIGCONT);
276✔
330

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

339
                        done = false;
373✔
340

341
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
373✔
342
                        if (r < 0)
373✔
UNCOV
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,248✔
350

351
        return ret;
352
}
353

354
int cg_kill_recursive(
17,102✔
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

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

366
        assert(path);
17,102✔
367
        assert(sig >= 0);
17,102✔
368

369
        if (!killed_pids) {
17,102✔
370
                killed_pids = allocated_set = set_new(NULL);
16,481✔
371
                if (!killed_pids)
16,481✔
372
                        return -ENOMEM;
373
        }
374

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

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

382
                return ret;
12,582✔
383
        }
384

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

388
                r = cg_read_subgroup(d, &fn);
4,590✔
389
                if (r < 0) {
4,590✔
UNCOV
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,590✔
394
                        break;
395

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

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

407
        return ret;
4,520✔
408
}
409

UNCOV
410
int cg_kill_kernel_sigkill(const char *path) {
×
UNCOV
411
        _cleanup_free_ char *killfile = NULL;
×
UNCOV
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

UNCOV
417
        assert(path);
×
418

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

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

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

430
        return 0;
431
}
432

UNCOV
433
static const char *controller_to_dirname(const char *controller) {
×
UNCOV
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

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

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

UNCOV
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

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

UNCOV
457
        dn = controller_to_dirname(controller);
×
458

UNCOV
459
        if (isempty(path) && isempty(suffix))
×
460
                t = path_join("/sys/fs/cgroup", dn);
×
UNCOV
461
        else if (isempty(path))
×
UNCOV
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
UNCOV
466
                t = path_join("/sys/fs/cgroup", dn, path, suffix);
×
467
        if (!t)
×
468
                return -ENOMEM;
469

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

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

477
        assert(ret);
237,453✔
478

479
        if (isempty(path) && isempty(suffix))
251,861✔
480
                t = strdup("/sys/fs/cgroup");
1,105✔
481
        else if (isempty(path))
236,348✔
482
                t = path_join("/sys/fs/cgroup", suffix);
13,303✔
483
        else if (isempty(suffix))
223,045✔
484
                t = path_join("/sys/fs/cgroup", path);
81,699✔
485
        else
486
                t = path_join("/sys/fs/cgroup", path, suffix);
141,346✔
487
        if (!t)
237,453✔
488
                return -ENOMEM;
489

490
        *ret = t;
237,453✔
491
        return 0;
237,453✔
492
}
493

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

497
        assert(ret);
237,453✔
498

499
        if (!controller) {
237,453✔
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))
×
UNCOV
509
                        t = strdup(path);
×
UNCOV
510
                else if (isempty(path))
×
511
                        t = strdup(suffix);
×
512
                else
UNCOV
513
                        t = path_join(path, suffix);
×
UNCOV
514
                if (!t)
×
515
                        return -ENOMEM;
516

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

521
        if (!cg_controller_is_valid(controller))
237,453✔
522
                return -EINVAL;
523

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

534
        path_simplify(*ret);
237,453✔
535
        return 0;
237,453✔
536
}
537

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

UNCOV
541
        assert(controller);
×
542

UNCOV
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

UNCOV
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) {
18,749✔
554
        int r;
18,749✔
555

556
        assert(controller);
18,749✔
557
        assert(ret);
18,749✔
558

559
        if (!cg_controller_is_valid(controller))
18,749✔
560
                return -EINVAL;
561

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

577
        return cg_get_path(controller, path, suffix, ret);
18,749✔
578
}
579

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

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

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

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

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

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

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

606
        return lgetxattr_malloc(fs, name, ret, ret_size);
15,872✔
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,878✔
624
        _cleanup_free_ char *fs = NULL;
24,878✔
625
        int r;
24,878✔
626

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

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

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

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

642
        assert(pid >= 0);
42,248✔
643
        assert(ret_path);
42,248✔
644

645
        if (controller) {
42,248✔
646
                if (!cg_controller_is_valid(controller))
42,062✔
647
                        return -EINVAL;
648
        } else
649
                controller = SYSTEMD_CGROUP_CONTROLLER;
650

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

661
        fs = procfs_file_alloca(pid, "cgroup");
49,148✔
662
        r = fopen_unlocked(fs, "re", &f);
42,248✔
663
        if (r == -ENOENT)
42,248✔
664
                return -ESRCH;
665
        if (r < 0)
37,642✔
666
                return r;
667

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

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

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

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

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

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

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

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

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

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

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

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

729
        assert(ret_path);
10,881✔
730

731
        if (!pidref_is_set(pidref))
10,881✔
732
                return -ESRCH;
733
        if (pidref_is_remote(pidref))
21,762✔
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);
10,881✔
741
        if (r < 0)
10,881✔
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);
10,880✔
746
        if (r < 0)
10,880✔
747
                return r;
748

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

753
int cg_is_empty(const char *controller, const char *path) {
6,424✔
754
        _cleanup_free_ char *t = NULL;
6,424✔
755
        int r;
6,424✔
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);
6,424✔
761

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

766
        r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.events", STRV_MAKE("populated"), &t);
6,424✔
767
        if (r == -ENOENT)
6,424✔
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) {
121✔
834
        _cleanup_free_ char *c = NULL, *p = NULL;
121✔
835
        int r;
121✔
836

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

840
        /* First, check if it already is a filesystem path */
841
        if (path_startswith(path, "/sys/fs/cgroup"))
121✔
842
                return path_simplify_alloc(path, ret);
117✔
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) {
13,979✔
853
        char *p, *e;
13,979✔
854
        int r;
13,979✔
855

856
        assert(ret_path);
13,979✔
857

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

862
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
13,979✔
863
        if (e)
13,979✔
864
                *e = 0;
13,949✔
865

866
        *ret_path = p;
13,979✔
867
        return 0;
13,979✔
868
}
869

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

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

876
        _cleanup_free_ char *rt = NULL;
11,334✔
877
        if (!root) {
11,334✔
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,989✔
882
                if (r < 0)
1,989✔
883
                        return r;
884

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

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

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

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

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

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

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

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

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

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

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

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

930
        return strdup_to(ret_unit, c);
32,501✔
931
}
932

933
static bool valid_slice_name(const char *p, size_t n) {
115,932✔
934

935
        if (!p)
115,932✔
936
                return false;
937

938
        if (n < STRLEN("x.slice"))
115,917✔
939
                return false;
940

941
        if (memcmp(p + n - 6, ".slice", 6) == 0) {
115,899✔
942
                char buf[n+1], *c;
58,826✔
943

944
                memcpy(buf, p, n);
58,826✔
945
                buf[n] = 0;
58,826✔
946

947
                c = cg_unescape(buf);
58,826✔
948

949
                return unit_name_is_valid(c, UNIT_NAME_PLAIN);
58,826✔
950
        }
951

952
        return false;
953
}
954

955
static const char *skip_slices(const char *p) {
41,259✔
956
        assert(p);
41,259✔
957

958
        /* Skips over all slice assignments */
959

960
        for (;;) {
126,179✔
961
                size_t n;
83,719✔
962

963
                p += strspn(p, "/");
83,719✔
964

965
                n = strcspn(p, "/");
83,719✔
966
                if (!valid_slice_name(p, n))
83,719✔
967
                        return p;
41,259✔
968

969
                p += n;
42,460✔
970
        }
971
}
972

973
int cg_path_get_unit(const char *path, char **ret) {
16,893✔
974
        _cleanup_free_ char *unit = NULL;
16,893✔
975
        const char *e;
16,893✔
976
        int r;
16,893✔
977

978
        assert(path);
16,893✔
979
        assert(ret);
16,893✔
980

981
        e = skip_slices(path);
16,893✔
982

983
        r = cg_path_decode_unit(e, &unit);
16,893✔
984
        if (r < 0)
16,893✔
985
                return r;
986

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

991
        *ret = TAKE_PTR(unit);
16,882✔
992
        return 0;
16,882✔
993
}
994

995
int cg_path_get_unit_path(const char *path, char **ret) {
9,179✔
996
        _cleanup_free_ char *path_copy = NULL;
9,179✔
997
        char *unit_name;
9,179✔
998

999
        assert(path);
9,179✔
1000
        assert(ret);
9,179✔
1001

1002
        path_copy = strdup(path);
9,179✔
1003
        if (!path_copy)
9,179✔
1004
                return -ENOMEM;
1005

1006
        unit_name = (char *)skip_slices(path_copy);
9,179✔
1007
        unit_name[strcspn(unit_name, "/")] = 0;
9,179✔
1008

1009
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,179✔
1010
                return -ENXIO;
1011

1012
        *ret = TAKE_PTR(path_copy);
9,176✔
1013

1014
        return 0;
9,176✔
1015
}
1016

1017
int cg_pid_get_unit(pid_t pid, char **ret_unit) {
628✔
1018
        _cleanup_free_ char *cgroup = NULL;
628✔
1019
        int r;
628✔
1020

1021
        assert(ret_unit);
628✔
1022

1023
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
628✔
1024
        if (r < 0)
628✔
1025
                return r;
1026

1027
        return cg_path_get_unit(cgroup, ret_unit);
624✔
1028
}
1029

1030
int cg_pidref_get_unit(const PidRef *pidref, char **ret) {
552✔
1031
        _cleanup_free_ char *unit = NULL;
552✔
1032
        int r;
552✔
1033

1034
        assert(ret);
552✔
1035

1036
        if (!pidref_is_set(pidref))
552✔
1037
                return -ESRCH;
1038
        if (pidref_is_remote(pidref))
1,104✔
1039
                return -EREMOTE;
1040

1041
        r = cg_pid_get_unit(pidref->pid, &unit);
552✔
1042
        if (r < 0)
552✔
1043
                return r;
1044

1045
        r = pidref_verify(pidref);
548✔
1046
        if (r < 0)
548✔
1047
                return r;
1048

1049
        *ret = TAKE_PTR(unit);
548✔
1050
        return 0;
548✔
1051
}
1052

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

1059
        if (isempty(p))
14,776✔
1060
                return NULL;
1061

1062
        p += strspn(p, "/");
14,772✔
1063

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

1068
        if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) {
14,608✔
1069
                char buf[n - 8 - 6 + 1];
21✔
1070

1071
                memcpy(buf, p + 8, n - 8 - 6);
21✔
1072
                buf[n - 8 - 6] = 0;
21✔
1073

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

1079
                if (!session_id_valid(buf))
21✔
1080
                        return NULL;
21✔
1081

1082
                p += n;
21✔
1083
                p += strspn(p, "/");
21✔
1084
                return p;
21✔
1085
        }
1086

1087
        return NULL;
1088
}
1089

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

1096
        if (isempty(p))
15,187✔
1097
                return NULL;
15,187✔
1098

1099
        p += strspn(p, "/");
15,183✔
1100

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

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

1111
        _cleanup_free_ char *i = NULL;
15,019✔
1112
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
15,019✔
1113

1114
        if (type != UNIT_NAME_INSTANCE)
15,019✔
1115
                return NULL;
1116

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

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

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

1136
        return NULL;
1137
}
1138

1139
static const char *skip_user_prefix(const char *path) {
15,187✔
1140
        const char *e, *t;
15,187✔
1141

1142
        assert(path);
15,187✔
1143

1144
        /* Skip slices, if there are any */
1145
        e = skip_slices(path);
15,187✔
1146

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

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

1156
int cg_path_get_user_unit(const char *path, char **ret) {
7,619✔
1157
        const char *t;
7,619✔
1158

1159
        assert(path);
7,619✔
1160
        assert(ret);
7,619✔
1161

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

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

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

1175
        assert(ret_unit);
52✔
1176

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

1181
        return cg_path_get_user_unit(cgroup, ret_unit);
52✔
1182
}
1183

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

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

1193
        sl = strjoina("/run/systemd/machines/unit:", u);
180✔
1194
        return readlink_malloc(sl, ret_machine);
36✔
1195
}
1196

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

1201
        assert(ret_machine);
36✔
1202

1203
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
36✔
1204
        if (r < 0)
36✔
1205
                return r;
1206

1207
        return cg_path_get_machine_name(cgroup, ret_machine);
36✔
1208
}
1209

1210
int cg_path_get_session(const char *path, char **ret_session) {
8,389✔
1211
        _cleanup_free_ char *unit = NULL;
8,389✔
1212
        char *start, *end;
8,389✔
1213
        int r;
8,389✔
1214

1215
        assert(path);
8,389✔
1216

1217
        r = cg_path_get_unit(path, &unit);
8,389✔
1218
        if (r < 0)
8,389✔
1219
                return r;
1220

1221
        start = startswith(unit, "session-");
8,388✔
1222
        if (!start)
8,388✔
1223
                return -ENXIO;
1224
        end = endswith(start, ".scope");
329✔
1225
        if (!end)
329✔
1226
                return -ENXIO;
1227

1228
        *end = 0;
329✔
1229
        if (!session_id_valid(start))
329✔
1230
                return -ENXIO;
1231

1232
        if (!ret_session)
328✔
1233
                return 0;
1234

1235
        return strdup_to(ret_session, start);
328✔
1236
}
1237

1238
int cg_pid_get_session(pid_t pid, char **ret_session) {
759✔
1239
        _cleanup_free_ char *cgroup = NULL;
759✔
1240
        int r;
759✔
1241

1242
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
759✔
1243
        if (r < 0)
759✔
1244
                return r;
1245

1246
        return cg_path_get_session(cgroup, ret_session);
759✔
1247
}
1248

1249
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
337✔
1250
        int r;
337✔
1251

1252
        if (!pidref_is_set(pidref))
337✔
1253
                return -ESRCH;
337✔
1254
        if (pidref_is_remote(pidref))
674✔
1255
                return -EREMOTE;
1256

1257
        _cleanup_free_ char *session = NULL;
337✔
1258
        r = cg_pid_get_session(pidref->pid, &session);
337✔
1259
        if (r < 0)
337✔
1260
                return r;
1261

1262
        r = pidref_verify(pidref);
286✔
1263
        if (r < 0)
286✔
1264
                return r;
1265

1266
        if (ret)
286✔
1267
                *ret = TAKE_PTR(session);
286✔
1268
        return 0;
1269
}
1270

1271
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
8,020✔
1272
        _cleanup_free_ char *slice = NULL;
8,020✔
1273
        char *start, *end;
8,020✔
1274
        int r;
8,020✔
1275

1276
        assert(path);
8,020✔
1277

1278
        r = cg_path_get_slice(path, &slice);
8,020✔
1279
        if (r < 0)
8,020✔
1280
                return r;
1281

1282
        start = startswith(slice, "user-");
8,020✔
1283
        if (!start)
8,020✔
1284
                return -ENXIO;
1285

1286
        end = endswith(start, ".slice");
474✔
1287
        if (!end)
474✔
1288
                return -ENXIO;
1289

1290
        *end = 0;
474✔
1291
        if (parse_uid(start, ret_uid) < 0)
474✔
UNCOV
1292
                return -ENXIO;
×
1293

1294
        return 0;
1295
}
1296

1297
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
411✔
1298
        _cleanup_free_ char *cgroup = NULL;
411✔
1299
        int r;
411✔
1300

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

1305
        return cg_path_get_owner_uid(cgroup, ret_uid);
411✔
1306
}
1307

1308
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
49✔
1309
        int r;
49✔
1310

1311
        if (!pidref_is_set(pidref))
49✔
1312
                return -ESRCH;
49✔
1313
        if (pidref_is_remote(pidref))
49✔
1314
                return -EREMOTE;
1315

1316
        uid_t uid;
49✔
1317
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
49✔
1318
        if (r < 0)
49✔
1319
                return r;
1320

1321
        r = pidref_verify(pidref);
8✔
1322
        if (r < 0)
8✔
1323
                return r;
1324

1325
        if (ret)
8✔
1326
                *ret = uid;
8✔
1327

1328
        return 0;
1329
}
1330

1331
int cg_path_get_slice(const char *p, char **ret_slice) {
15,847✔
1332
        const char *e = NULL;
15,847✔
1333

1334
        assert(p);
15,847✔
1335
        assert(ret_slice);
15,847✔
1336

1337
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1338
         * the first non-slice unit. */
1339

1340
        for (;;) {
48,579✔
1341
                const char *s;
32,213✔
1342
                int n;
32,213✔
1343

1344
                n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s);
32,213✔
1345
                if (n < 0)
32,213✔
UNCOV
1346
                        return n;
×
1347
                if (!valid_slice_name(s, n))
32,213✔
1348
                        break;
1349

1350
                e = s;
16,366✔
1351
        }
1352

1353
        if (e)
15,847✔
1354
                return cg_path_decode_unit(e, ret_slice);
15,614✔
1355

1356
        return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
233✔
1357
}
1358

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

1363
        assert(ret_slice);
56✔
1364

1365
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
56✔
1366
        if (r < 0)
56✔
1367
                return r;
1368

1369
        return cg_path_get_slice(cgroup, ret_slice);
56✔
1370
}
1371

1372
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,568✔
1373
        const char *t;
7,568✔
1374
        assert(p);
7,568✔
1375
        assert(ret_slice);
7,568✔
1376

1377
        t = skip_user_prefix(p);
7,568✔
1378
        if (!t)
7,568✔
1379
                return -ENXIO;
1380

1381
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1382
         * from here on. */
1383
        return cg_path_get_slice(t, ret_slice);
210✔
1384
}
1385

1386
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1387
        _cleanup_free_ char *cgroup = NULL;
1✔
1388
        int r;
1✔
1389

1390
        assert(ret_slice);
1✔
1391

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

1396
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1397
}
1398

1399
bool cg_needs_escape(const char *p) {
12,683✔
1400

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

1406
        if (!filename_is_valid(p))
12,683✔
1407
                return true;
1408

1409
        if (IN_SET(p[0], '_', '.'))
12,679✔
1410
                return true;
1411

1412
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
12,673✔
1413
                return true;
2✔
1414

1415
        if (startswith(p, "cgroup."))
12,671✔
1416
                return true;
1417

1418
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
177,366✔
1419
                const char *q;
164,697✔
1420

1421
                q = startswith(p, cgroup_controller_to_string(c));
164,697✔
1422
                if (!q)
164,697✔
1423
                        continue;
164,697✔
1424

UNCOV
1425
                if (q[0] == '.')
×
1426
                        return true;
1427
        }
1428

1429
        return false;
1430
}
1431

1432
int cg_escape(const char *p, char **ret) {
12,428✔
1433
        _cleanup_free_ char *n = NULL;
12,428✔
1434

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

1440
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1441

1442
        if (cg_needs_escape(p)) {
12,428✔
1443
                n = strjoin("_", p);
7✔
1444
                if (!n)
7✔
1445
                        return -ENOMEM;
1446

1447
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1448
                        return -EINVAL;
1449
        } else {
1450
                n = strdup(p);
12,421✔
1451
                if (!n)
12,421✔
1452
                        return -ENOMEM;
1453
        }
1454

1455
        *ret = TAKE_PTR(n);
12,428✔
1456
        return 0;
12,428✔
1457
}
1458

1459
char* cg_unescape(const char *p) {
100,565✔
1460
        assert(p);
100,565✔
1461

1462
        /* The return value of this function (unlike cg_escape())
1463
         * doesn't need free()! */
1464

1465
        if (p[0] == '_')
100,565✔
1466
                return (char*) p+1;
14✔
1467

1468
        return (char*) p;
1469
}
1470

1471
#define CONTROLLER_VALID                        \
1472
        DIGITS LETTERS                          \
1473
        "_"
1474

1475
bool cg_controller_is_valid(const char *p) {
298,282✔
1476
        const char *t, *s;
298,282✔
1477

1478
        if (!p)
298,282✔
1479
                return false;
1480

1481
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
298,282✔
1482
                return true;
1483

1484
        s = startswith(p, "name=");
91,783✔
1485
        if (s)
91,783✔
1486
                p = s;
2✔
1487

1488
        if (IN_SET(*p, 0, '_'))
91,783✔
1489
                return false;
1490

1491
        for (t = p; *t; t++)
589,541✔
1492
                if (!strchr(CONTROLLER_VALID, *t))
497,769✔
1493
                        return false;
1494

1495
        if (t - p > NAME_MAX)
91,772✔
UNCOV
1496
                return false;
×
1497

1498
        return true;
1499
}
1500

1501
int cg_slice_to_path(const char *unit, char **ret) {
5,030✔
1502
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
5,030✔
1503
        const char *dash;
5,030✔
1504
        int r;
5,030✔
1505

1506
        assert(unit);
5,030✔
1507
        assert(ret);
5,030✔
1508

1509
        if (streq(unit, SPECIAL_ROOT_SLICE))
5,030✔
1510
                return strdup_to(ret, "");
7✔
1511

1512
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
5,023✔
1513
                return -EINVAL;
1514

1515
        if (!endswith(unit, ".slice"))
5,012✔
1516
                return -EINVAL;
1517

1518
        r = unit_name_to_prefix(unit, &p);
5,011✔
1519
        if (r < 0)
5,011✔
1520
                return r;
1521

1522
        dash = strchr(p, '-');
5,011✔
1523

1524
        /* Don't allow initial dashes */
1525
        if (dash == p)
5,011✔
1526
                return -EINVAL;
1527

1528
        while (dash) {
5,177✔
1529
                _cleanup_free_ char *escaped = NULL;
171✔
1530
                char n[dash - p + sizeof(".slice")];
171✔
1531

1532
#if HAS_FEATURE_MEMORY_SANITIZER
1533
                /* msan doesn't instrument stpncpy, so it thinks
1534
                 * n is later used uninitialized:
1535
                 * https://github.com/google/sanitizers/issues/926
1536
                 */
1537
                zero(n);
1538
#endif
1539

1540
                /* Don't allow trailing or double dashes */
1541
                if (IN_SET(dash[1], 0, '-'))
171✔
1542
                        return -EINVAL;
1543

1544
                strcpy(stpncpy(n, p, dash - p), ".slice");
169✔
1545
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
169✔
1546
                        return -EINVAL;
1547

1548
                r = cg_escape(n, &escaped);
169✔
1549
                if (r < 0)
169✔
1550
                        return r;
1551

1552
                if (!strextend(&s, escaped, "/"))
169✔
1553
                        return -ENOMEM;
1554

1555
                dash = strchr(dash+1, '-');
169✔
1556
        }
1557

1558
        r = cg_escape(unit, &e);
5,006✔
1559
        if (r < 0)
5,006✔
1560
                return r;
1561

1562
        if (!strextend(&s, e))
5,006✔
1563
                return -ENOMEM;
1564

1565
        *ret = TAKE_PTR(s);
5,006✔
1566
        return 0;
5,006✔
1567
}
1568

UNCOV
1569
int cg_is_threaded(const char *path) {
×
UNCOV
1570
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
UNCOV
1571
        _cleanup_strv_free_ char **v = NULL;
×
UNCOV
1572
        int r;
×
1573

UNCOV
1574
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.type", &fs);
×
UNCOV
1575
        if (r < 0)
×
1576
                return r;
1577

UNCOV
1578
        r = read_full_virtual_file(fs, &contents, NULL);
×
UNCOV
1579
        if (r == -ENOENT)
×
1580
                return false; /* Assume no. */
UNCOV
1581
        if (r < 0)
×
1582
                return r;
1583

UNCOV
1584
        v = strv_split(contents, NULL);
×
UNCOV
1585
        if (!v)
×
1586
                return -ENOMEM;
1587

1588
        /* If the cgroup is in the threaded mode, it contains "threaded".
1589
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
UNCOV
1590
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1591
}
1592

1593
int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) {
34,563✔
1594
        _cleanup_free_ char *p = NULL;
34,563✔
1595
        int r;
34,563✔
1596

1597
        assert(attribute);
34,563✔
1598

1599
        r = cg_get_path(controller, path, attribute, &p);
34,563✔
1600
        if (r < 0)
34,563✔
1601
                return r;
1602

1603
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER);
34,563✔
1604
}
1605

1606
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) {
28,923✔
1607
        _cleanup_free_ char *p = NULL;
28,923✔
1608
        int r;
28,923✔
1609

1610
        assert(attribute);
28,923✔
1611

1612
        r = cg_get_path(controller, path, attribute, &p);
28,923✔
1613
        if (r < 0)
28,923✔
1614
                return r;
1615

1616
        return read_one_line_file(p, ret);
28,923✔
1617
}
1618

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

1624
        assert(ret);
24,284✔
1625

1626
        r = cg_get_attribute(controller, path, attribute, &value);
24,284✔
1627
        if (r == -ENOENT)
24,284✔
1628
                return -ENODATA;
1629
        if (r < 0)
22,715✔
1630
                return r;
1631

1632
        if (streq(value, "max")) {
22,715✔
1633
                *ret = CGROUP_LIMIT_MAX;
5,553✔
1634
                return 0;
5,553✔
1635
        }
1636

1637
        r = safe_atou64(value, &v);
17,162✔
1638
        if (r < 0)
17,162✔
1639
                return r;
1640

1641
        *ret = v;
17,162✔
1642
        return 0;
17,162✔
1643
}
1644

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

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

1655
        return parse_boolean(value);
61✔
1656
}
1657

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

1663
        assert(ret_uid);
35✔
1664

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

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

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

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

1680
int cg_get_keyed_attribute(
33,147✔
1681
                const char *controller,
1682
                const char *path,
1683
                const char *attribute,
1684
                char * const *keys,
1685
                char **values) {
1686

1687
        _cleanup_free_ char *filename = NULL, *contents = NULL;
33,147✔
1688
        size_t n;
33,147✔
1689
        int r;
33,147✔
1690

1691
        assert(path);
33,147✔
1692
        assert(attribute);
33,147✔
1693

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

1700
        r = cg_get_path(controller, path, attribute, &filename);
33,147✔
1701
        if (r < 0)
33,147✔
1702
                return r;
1703

1704
        r = read_full_file(filename, &contents, /* ret_size = */ NULL);
33,147✔
1705
        if (r < 0)
33,147✔
1706
                return r;
1707

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

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

1717
        for (const char *p = contents; *p;) {
68,563✔
1718
                const char *w;
1719
                size_t i;
1720

1721
                for (i = 0; i < n; i++) {
116,928✔
1722
                        w = first_word(p, keys[i]);
75,558✔
1723
                        if (w)
75,558✔
1724
                                break;
1725
                }
1726

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

1733
                        size_t l = strcspn(w, NEWLINE);
27,193✔
1734

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

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

1745
                        p = w + l;
6,995✔
1746
                } else
1747
                        p += strcspn(p, NEWLINE);
41,370✔
1748

1749
                p += strspn(p, NEWLINE);
48,365✔
1750
        }
1751

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

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

UNCOV
1760
fail:
×
1761
        free_many_charp(v, n);
33,147✔
1762
        return r;
1763
}
1764

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

1771
        assert(ret);
11,996✔
1772

1773
        if (mask == 0) {
11,996✔
1774
                *ret = NULL;
4,937✔
1775
                return 0;
4,937✔
1776
        }
1777

1778
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
98,826✔
1779
                const char *k;
91,767✔
1780
                size_t l;
91,767✔
1781

1782
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
91,767✔
1783
                        continue;
32,642✔
1784

1785
                k = cgroup_controller_to_string(c);
59,125✔
1786
                l = strlen(k);
59,125✔
1787

1788
                if (!GREEDY_REALLOC(s, n + space + l + 1))
59,125✔
1789
                        return -ENOMEM;
1790

1791
                if (space)
59,125✔
1792
                        s[n] = ' ';
52,066✔
1793
                memcpy(s + n + space, k, l);
59,125✔
1794
                n += space + l;
59,125✔
1795

1796
                space = true;
59,125✔
1797
        }
1798

1799
        assert(s);
7,059✔
1800

1801
        s[n] = 0;
7,059✔
1802
        *ret = TAKE_PTR(s);
7,059✔
1803

1804
        return 0;
7,059✔
1805
}
1806

1807
int cg_mask_from_string(const char *value, CGroupMask *ret) {
6,473✔
1808
        CGroupMask m = 0;
6,473✔
1809

1810
        assert(ret);
6,473✔
1811
        assert(value);
6,473✔
1812

1813
        for (;;) {
58,171✔
1814
                _cleanup_free_ char *n = NULL;
51,698✔
1815
                CGroupController v;
58,171✔
1816
                int r;
58,171✔
1817

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

1824
                v = cgroup_controller_from_string(n);
51,698✔
1825
                if (v < 0)
51,698✔
1826
                        continue;
642✔
1827

1828
                m |= CGROUP_CONTROLLER_TO_MASK(v);
51,056✔
1829
        }
1830

1831
        *ret = m;
6,473✔
1832
        return 0;
6,473✔
1833
}
1834

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

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

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

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

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

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

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

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

1867
        } else {
UNCOV
1868
                CGroupController c;
×
1869

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

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

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

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

1886
        *ret = mask;
467✔
1887
        return 0;
467✔
1888
}
1889

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

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

1898
        return cg_mask_supported_subtree(root, ret);
218✔
1899
}
1900

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

1913
int cg_unified_cached(bool flush) {
304,138✔
1914
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
304,138✔
1915

1916
        struct statfs fs;
304,138✔
1917

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

1923
        if (flush)
304,138✔
1924
                unified_cache = CGROUP_UNIFIED_UNKNOWN;
4✔
1925
        else if (unified_cache >= CGROUP_UNIFIED_NONE)
304,134✔
1926
                return unified_cache;
304,138✔
1927

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

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

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

1971
        return unified_cache;
12,960✔
1972
}
1973

1974
int cg_unified_controller(const char *controller) {
42,249✔
1975
        int r;
42,249✔
1976

1977
        r = cg_unified_cached(false);
42,249✔
1978
        if (r < 0)
42,249✔
1979
                return r;
1980

1981
        if (r == CGROUP_UNIFIED_NONE)
42,249✔
1982
                return false;
1983

1984
        if (r >= CGROUP_UNIFIED_ALL)
42,249✔
1985
                return true;
1986

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

1990
int cg_all_unified(void) {
261,883✔
1991
        int r;
261,883✔
1992

1993
        r = cg_unified_cached(false);
261,883✔
1994
        if (r < 0)
261,883✔
1995
                return r;
1996

1997
        return r >= CGROUP_UNIFIED_ALL;
261,883✔
1998
}
1999

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

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

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

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

2013
        assert(path);
19✔
2014

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

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

2026
int cg_is_delegated_fd(int fd) {
42✔
2027
        int r;
42✔
2028

2029
        assert(fd >= 0);
42✔
2030

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

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

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

2042
        assert(path);
2✔
2043

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

2048
        return r;
2049
}
2050

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

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

2065
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
5,301✔
2066

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

2083
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
296,885✔
2084

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

2090
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
31,461✔
2091

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

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