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

systemd / systemd / 13125386108

03 Feb 2025 10:14PM UTC coverage: 71.734% (-0.06%) from 71.79%
13125386108

push

github

web-flow
docs: Update CPE fields in package metadata spec (#36251)

Update osCPE field example to use cpe 2.3 format, as is in active use by
AmazonLinux 2023 for example.

Add appCPE field example to document the upstream application CPE for
the applicable CVEs. Often distribution source package names are
different from the upstream CPE. For example adding/removing "lib"
prefix, or adding version stream "-3" suffix. This typically leads to
guessing or fuzzy matching. Adding appCPE in such cases can help to
disambiguate (or collate) correct application CPEs; especially beyond
the lifetime of osCPE support timeframes. This also will help a lot with
packaging multiple alternative source packages of the same software
(e.g. nginx-full nginx-core); different version streams (e.g.
openssl-1.1, openssl-3); or alternative builds of upstream software with
largely the same CVEs with multiple version streams (e.g.
openjdk-{22,17,11..}, corretto-{22,17,11..}, temurin-{22,17,11..}, etc).

292625 of 407928 relevant lines covered (71.73%)

713708.1 hits per line

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

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

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

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

43
int cg_path_open(const char *controller, const char *path) {
776✔
44
        _cleanup_free_ char *fs = NULL;
776✔
45
        int r;
776✔
46

47
        r = cg_get_path(controller, path, /* item=*/ NULL, &fs);
776✔
48
        if (r < 0)
776✔
49
                return r;
50

51
        return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
776✔
52
}
53

54
int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) {
1✔
55
        _cleanup_close_ int fsfd = -EBADF;
1✔
56

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

62
                cgroupfs_fd = fsfd;
63
        }
64

65
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
1✔
66
        CG_FILE_HANDLE_CGROUPID(fh) = id;
1✔
67

68
        return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
2✔
69
}
70

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

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

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

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

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

92
int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret) {
5,737✔
93
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
5,737✔
94
        int mnt_id;
5,737✔
95

96
        assert(dfd >= 0 || (dfd == AT_FDCWD && path_is_absolute(path)));
11,443✔
97
        assert(ret);
5,737✔
98

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

106
        *ret = CG_FILE_HANDLE_CGROUPID(fh);
5,737✔
107
        return 0;
5,737✔
108
}
109

110
static int cg_enumerate_items(const char *controller, const char *path, FILE **ret, const char *item) {
29,366✔
111
        _cleanup_free_ char *fs = NULL;
29,366✔
112
        FILE *f;
29,366✔
113
        int r;
29,366✔
114

115
        assert(ret);
29,366✔
116

117
        r = cg_get_path(controller, path, item, &fs);
29,366✔
118
        if (r < 0)
29,366✔
119
                return r;
120

121
        f = fopen(fs, "re");
29,366✔
122
        if (!f)
29,366✔
123
                return -errno;
18,334✔
124

125
        *ret = f;
11,032✔
126
        return 0;
11,032✔
127
}
128

129
int cg_enumerate_processes(const char *controller, const char *path, FILE **ret) {
410✔
130
        return cg_enumerate_items(controller, path, ret, "cgroup.procs");
410✔
131
}
132

133
int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags) {
22,053✔
134
        unsigned long ul;
22,053✔
135

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

138
        assert(f);
22,053✔
139
        assert(ret);
22,053✔
140

141
        for (;;) {
22,053✔
142
                errno = 0;
22,053✔
143
                if (fscanf(f, "%lu", &ul) != 1) {
22,053✔
144

145
                        if (feof(f)) {
11,262✔
146
                                *ret = 0;
11,262✔
147
                                return 0;
11,262✔
148
                        }
149

150
                        return errno_or_else(EIO);
×
151
                }
152

153
                if (ul > PID_T_MAX)
10,791✔
154
                        return -EIO;
155

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

162
                *ret = (pid_t) ul;
10,791✔
163
                return 1;
10,791✔
164
        }
165
}
166

167
int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) {
15,103✔
168
        int r;
15,103✔
169

170
        assert(f);
15,103✔
171
        assert(ret);
15,103✔
172

173
        for (;;) {
×
174
                pid_t pid;
15,103✔
175

176
                r = cg_read_pid(f, &pid, flags);
15,103✔
177
                if (r < 0)
15,103✔
178
                        return log_debug_errno(r, "Failed to read pid from cgroup item: %m");
×
179
                if (r == 0) {
15,103✔
180
                        *ret = PIDREF_NULL;
10,640✔
181
                        return 0;
10,640✔
182
                }
183

184
                if (pid == 0)
4,463✔
185
                        return -EREMOTE;
186

187
                if (FLAGS_SET(flags, CGROUP_NO_PIDFD)) {
4,463✔
188
                        *ret = PIDREF_MAKE_FROM_PID(pid);
527✔
189
                        return 1;
527✔
190
                }
191

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

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

202
int cg_read_event(
12,350✔
203
                const char *controller,
204
                const char *path,
205
                const char *event,
206
                char **ret) {
207

208
        _cleanup_free_ char *events = NULL, *content = NULL;
12,350✔
209
        int r;
12,350✔
210

211
        r = cg_get_path(controller, path, "cgroup.events", &events);
12,350✔
212
        if (r < 0)
12,350✔
213
                return r;
214

215
        r = read_full_virtual_file(events, &content, NULL);
12,350✔
216
        if (r < 0)
12,350✔
217
                return r;
218

219
        for (const char *p = content;;) {
4,757✔
220
                _cleanup_free_ char *line = NULL, *key = NULL;
4,757✔
221
                const char *q;
4,757✔
222

223
                r = extract_first_word(&p, &line, "\n", 0);
4,757✔
224
                if (r < 0)
4,757✔
225
                        return r;
226
                if (r == 0)
4,757✔
227
                        return -ENOENT;
228

229
                q = line;
4,757✔
230
                r = extract_first_word(&q, &key, " ", 0);
4,757✔
231
                if (r < 0)
4,757✔
232
                        return r;
233
                if (r == 0)
4,757✔
234
                        return -EINVAL;
235

236
                if (!streq(key, event))
4,757✔
237
                        continue;
×
238

239
                return strdup_to(ret, q);
4,757✔
240
        }
241
}
242

243
bool cg_ns_supported(void) {
588✔
244
        static thread_local int supported = -1;
588✔
245

246
        if (supported >= 0)
588✔
247
                return supported;
×
248

249
        if (access("/proc/self/ns/cgroup", F_OK) >= 0)
588✔
250
                return (supported = true);
588✔
251
        if (errno != ENOENT)
×
252
                log_debug_errno(errno, "Failed to check whether /proc/self/ns/cgroup is available, assuming not: %m");
×
253
        return (supported = false);
×
254
}
255

256
bool cg_freezer_supported(void) {
×
257
        static thread_local int supported = -1;
×
258

259
        if (supported >= 0)
×
260
                return supported;
×
261

262
        if (cg_all_unified() <= 0)
×
263
                return (supported = false);
×
264

265
        if (access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) >= 0)
×
266
                return (supported = true);
×
267
        if (errno != ENOENT)
×
268
                log_debug_errno(errno, "Failed to check whether cgroup freezer is available, assuming not: %m");
×
269
        return (supported = false);
×
270
}
271

272
bool cg_kill_supported(void) {
×
273
        static thread_local int supported = -1;
×
274

275
        if (supported >= 0)
×
276
                return supported;
×
277

278
        if (cg_all_unified() <= 0)
×
279
                return (supported = false);
×
280

281
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
×
282
                return (supported = true);
×
283
        if (errno != ENOENT)
×
284
                log_debug_errno(errno, "Failed to check whether cgroup.kill is available, assuming not: %m");
×
285
        return (supported = false);
×
286
}
287

288
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret) {
27,182✔
289
        _cleanup_free_ char *fs = NULL;
27,182✔
290
        DIR *d;
27,182✔
291
        int r;
27,182✔
292

293
        assert(ret);
27,182✔
294

295
        /* This is not recursive! */
296

297
        r = cg_get_path(controller, path, NULL, &fs);
27,182✔
298
        if (r < 0)
27,182✔
299
                return r;
300

301
        d = opendir(fs);
27,182✔
302
        if (!d)
27,182✔
303
                return -errno;
15,760✔
304

305
        *ret = d;
11,422✔
306
        return 0;
11,422✔
307
}
308

309
int cg_read_subgroup(DIR *d, char **ret) {
16,931✔
310
        assert(d);
16,931✔
311
        assert(ret);
16,931✔
312

313
        FOREACH_DIRENT_ALL(de, d, return -errno) {
536,855✔
314
                if (de->d_type != DT_DIR)
525,203✔
315
                        continue;
496,620✔
316

317
                if (dot_or_dot_dot(de->d_name))
28,583✔
318
                        continue;
23,304✔
319

320
                return strdup_to_full(ret, de->d_name);
5,279✔
321
        }
322

323
        *ret = NULL;
11,652✔
324
        return 0;
11,652✔
325
}
326

327
static int cg_kill_items(
28,707✔
328
                const char *path,
329
                const char *item,
330
                int sig,
331
                CGroupFlags flags,
332
                Set *s,
333
                cg_kill_log_func_t log_kill,
334
                void *userdata) {
335

336
        _cleanup_set_free_ Set *allocated_set = NULL;
28,707✔
337
        int r, ret = 0;
28,707✔
338

339
        assert(path);
28,707✔
340
        assert(item);
28,707✔
341
        assert(sig >= 0);
28,707✔
342

343
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
344
          * don't send SIGCONT on SIGKILL. */
345
        if (IN_SET(sig, SIGCONT, SIGKILL))
28,707✔
346
                flags &= ~CGROUP_SIGCONT;
10,808✔
347

348
        /* This goes through the tasks list and kills them all. This is repeated until no further processes
349
         * are added to the tasks list, to properly handle forking processes.
350
         *
351
         * When sending SIGKILL, prefer cg_kill_kernel_sigkill(), which is fully atomic. */
352

353
        if (!s) {
28,707✔
354
                s = allocated_set = set_new(NULL);
898✔
355
                if (!s)
898✔
356
                        return -ENOMEM;
357
        }
358

359
        bool done;
28,956✔
360
        do {
28,956✔
361
                _cleanup_fclose_ FILE *f = NULL;
18,334✔
362
                int ret_log_kill;
28,956✔
363

364
                done = true;
28,956✔
365

366
                r = cg_enumerate_items(SYSTEMD_CGROUP_CONTROLLER, path, &f, item);
28,956✔
367
                if (r == -ENOENT)
28,956✔
368
                        break;
369
                if (r < 0)
10,622✔
370
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
371

372
                for (;;) {
15,003✔
373
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
15,003✔
374

375
                        r = cg_read_pidref(f, &pidref, flags);
15,003✔
376
                        if (r < 0)
15,003✔
377
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
378
                        if (r == 0)
15,003✔
379
                                break;
380

381
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
4,381✔
382
                                continue;
898✔
383

384
                        if (set_contains(s, PID_TO_PTR(pidref.pid)))
3,483✔
385
                                continue;
2,101✔
386

387
                        /* Ignore kernel threads to mimic the behavior of cgroup.kill. */
388
                        if (pidref_is_kernel_thread(&pidref) > 0) {
1,382✔
389
                                log_debug("Ignoring kernel thread with pid " PID_FMT " in cgroup '%s'", pidref.pid, path);
×
390
                                continue;
×
391
                        }
392

393
                        if (log_kill)
1,382✔
394
                                ret_log_kill = log_kill(&pidref, sig, userdata);
89✔
395

396
                        /* If we haven't killed this process yet, kill it */
397
                        r = pidref_kill(&pidref, sig);
1,382✔
398
                        if (r < 0 && r != -ESRCH)
1,382✔
399
                                RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path));
×
400
                        if (r >= 0) {
1,382✔
401
                                if (flags & CGROUP_SIGCONT)
1,382✔
402
                                        (void) pidref_kill(&pidref, SIGCONT);
1,291✔
403

404
                                if (ret == 0) {
1,382✔
405
                                        if (log_kill)
318✔
406
                                                ret = ret_log_kill;
407
                                        else
408
                                                ret = 1;
229✔
409
                                }
410
                        }
411

412
                        done = false;
1,382✔
413

414
                        r = set_put(s, PID_TO_PTR(pidref.pid));
1,382✔
415
                        if (r < 0)
1,382✔
416
                                return RET_GATHER(ret, r);
×
417
                }
418

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

422
        } while (!done);
10,622✔
423

424
        return ret;
425
}
426

427
int cg_kill(
23,303✔
428
                const char *path,
429
                int sig,
430
                CGroupFlags flags,
431
                Set *s,
432
                cg_kill_log_func_t log_kill,
433
                void *userdata) {
434

435
        int r, ret;
23,303✔
436

437
        assert(path);
23,303✔
438

439
        ret = cg_kill_items(path, "cgroup.procs", sig, flags, s, log_kill, userdata);
23,303✔
440
        if (ret < 0)
23,303✔
441
                return log_debug_errno(ret, "Failed to kill processes in cgroup '%s' item cgroup.procs: %m", path);
×
442
        if (sig != SIGKILL)
23,303✔
443
                return ret;
444

445
        /* Only in case of killing with SIGKILL and when using cgroupsv2, kill remaining threads manually as
446
           a workaround for kernel bug. It was fixed in 5.2-rc5 (c03cd7738a83), backported to 4.19.66
447
           (4340d175b898) and 4.14.138 (feb6b123b7dd). */
448
        r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
5,404✔
449
        if (r < 0)
5,404✔
450
                return r;
451
        if (r == 0)
5,404✔
452
                return ret;
453

454
        /* Opening pidfds for non thread group leaders only works from 6.9 onwards with PIDFD_THREAD. On
455
         * older kernels or without PIDFD_THREAD pidfd_open() fails with EINVAL. Since we might read non
456
         * thread group leader IDs from cgroup.threads, we set CGROUP_NO_PIDFD to avoid trying open pidfd's
457
         * for them and instead use the regular pid. */
458
        r = cg_kill_items(path, "cgroup.threads", sig, flags|CGROUP_NO_PIDFD, s, log_kill, userdata);
5,404✔
459
        if (r < 0)
5,404✔
460
                return log_debug_errno(r, "Failed to kill processes in cgroup '%s' item cgroup.threads: %m", path);
×
461

462
        return r > 0 || ret > 0;
5,404✔
463
}
464

465
int cg_kill_recursive(
22,852✔
466
                const char *path,
467
                int sig,
468
                CGroupFlags flags,
469
                Set *s,
470
                cg_kill_log_func_t log_kill,
471
                void *userdata) {
472

473
        _cleanup_set_free_ Set *allocated_set = NULL;
×
474
        _cleanup_closedir_ DIR *d = NULL;
22,852✔
475
        int r, ret;
22,852✔
476

477
        assert(path);
22,852✔
478
        assert(sig >= 0);
22,852✔
479

480
        if (!s) {
22,852✔
481
                s = allocated_set = set_new(NULL);
21,943✔
482
                if (!s)
21,943✔
483
                        return -ENOMEM;
484
        }
485

486
        ret = cg_kill(path, sig, flags, s, log_kill, userdata);
22,852✔
487

488
        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d);
22,852✔
489
        if (r < 0) {
22,852✔
490
                if (r != -ENOENT)
15,760✔
491
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
492

493
                return ret;
15,760✔
494
        }
495

496
        for (;;) {
7,456✔
497
                _cleanup_free_ char *fn = NULL, *p = NULL;
7,274✔
498

499
                r = cg_read_subgroup(d, &fn);
7,274✔
500
                if (r < 0) {
7,274✔
501
                        RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path));
×
502
                        break;
503
                }
504
                if (r == 0)
7,274✔
505
                        break;
506

507
                p = path_join(empty_to_root(path), fn);
364✔
508
                if (!p)
182✔
509
                        return -ENOMEM;
×
510

511
                r = cg_kill_recursive(p, sig, flags, s, log_kill, userdata);
182✔
512
                if (r < 0)
182✔
513
                        log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p);
×
514
                if (r != 0 && ret >= 0)
182✔
515
                        ret = r;
15✔
516
        }
517

518
        return ret;
7,092✔
519
}
520

521
int cg_kill_kernel_sigkill(const char *path) {
×
522
        _cleanup_free_ char *killfile = NULL;
×
523
        int r;
×
524

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

528
        assert(path);
×
529

530
        if (!cg_kill_supported())
×
531
                return -EOPNOTSUPP;
532

533
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.kill", &killfile);
×
534
        if (r < 0)
×
535
                return r;
536

537
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
×
538
        if (r < 0)
×
539
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
540

541
        return 0;
542
}
543

544
static const char *controller_to_dirname(const char *controller) {
×
545
        assert(controller);
×
546

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

551
        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
×
552
                if (cg_hybrid_unified() > 0)
×
553
                        controller = SYSTEMD_CGROUP_CONTROLLER_HYBRID;
554
                else
555
                        controller = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
×
556
        }
557

558
        return startswith(controller, "name=") ?: controller;
×
559
}
560

561
static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **ret) {
×
562
        const char *dn;
×
563
        char *t = NULL;
×
564

565
        assert(ret);
×
566
        assert(controller);
×
567

568
        dn = controller_to_dirname(controller);
×
569

570
        if (isempty(path) && isempty(suffix))
×
571
                t = path_join("/sys/fs/cgroup", dn);
×
572
        else if (isempty(path))
×
573
                t = path_join("/sys/fs/cgroup", dn, suffix);
×
574
        else if (isempty(suffix))
×
575
                t = path_join("/sys/fs/cgroup", dn, path);
×
576
        else
577
                t = path_join("/sys/fs/cgroup", dn, path, suffix);
×
578
        if (!t)
×
579
                return -ENOMEM;
580

581
        *ret = t;
×
582
        return 0;
×
583
}
584

585
static int join_path_unified(const char *path, const char *suffix, char **ret) {
325,983✔
586
        char *t;
325,983✔
587

588
        assert(ret);
325,983✔
589

590
        if (isempty(path) && isempty(suffix))
341,862✔
591
                t = strdup("/sys/fs/cgroup");
2,585✔
592
        else if (isempty(path))
323,398✔
593
                t = path_join("/sys/fs/cgroup", suffix);
13,294✔
594
        else if (isempty(suffix))
310,104✔
595
                t = path_join("/sys/fs/cgroup", path);
121,047✔
596
        else
597
                t = path_join("/sys/fs/cgroup", path, suffix);
189,057✔
598
        if (!t)
325,983✔
599
                return -ENOMEM;
600

601
        *ret = t;
325,983✔
602
        return 0;
325,983✔
603
}
604

605
int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret) {
326,240✔
606
        int r;
326,240✔
607

608
        assert(ret);
326,240✔
609

610
        if (!controller) {
326,240✔
611
                char *t;
257✔
612

613
                /* If no controller is specified, we return the path *below* the controllers, without any
614
                 * prefix. */
615

616
                if (isempty(path) && isempty(suffix))
257✔
617
                        return -EINVAL;
618

619
                if (isempty(suffix))
257✔
620
                        t = strdup(path);
×
621
                else if (isempty(path))
257✔
622
                        t = strdup(suffix);
×
623
                else
624
                        t = path_join(path, suffix);
257✔
625
                if (!t)
257✔
626
                        return -ENOMEM;
627

628
                *ret = path_simplify(t);
257✔
629
                return 0;
257✔
630
        }
631

632
        if (!cg_controller_is_valid(controller))
325,983✔
633
                return -EINVAL;
634

635
        r = cg_all_unified();
325,983✔
636
        if (r < 0)
325,983✔
637
                return r;
638
        if (r > 0)
325,983✔
639
                r = join_path_unified(path, suffix, ret);
325,983✔
640
        else
641
                r = join_path_legacy(controller, path, suffix, ret);
×
642
        if (r < 0)
325,983✔
643
                return r;
644

645
        path_simplify(*ret);
325,983✔
646
        return 0;
325,983✔
647
}
648

649
static int controller_is_v1_accessible(const char *root, const char *controller) {
×
650
        const char *cpath, *dn;
×
651

652
        assert(controller);
×
653

654
        dn = controller_to_dirname(controller);
×
655

656
        /* If root if specified, we check that:
657
         * - possible subcgroup is created at root,
658
         * - we can modify the hierarchy. */
659

660
        cpath = strjoina("/sys/fs/cgroup/", dn, root, root ? "/cgroup.procs" : NULL);
×
661
        return access_nofollow(cpath, root ? W_OK : F_OK);
×
662
}
663

664
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret) {
23,972✔
665
        int r;
23,972✔
666

667
        assert(controller);
23,972✔
668
        assert(ret);
23,972✔
669

670
        if (!cg_controller_is_valid(controller))
23,972✔
671
                return -EINVAL;
672

673
        r = cg_all_unified();
23,972✔
674
        if (r < 0)
23,972✔
675
                return r;
676
        if (r > 0) {
23,972✔
677
                /* In the unified hierarchy all controllers are considered accessible,
678
                 * except for the named hierarchies */
679
                if (startswith(controller, "name="))
23,972✔
680
                        return -EOPNOTSUPP;
681
        } else {
682
                /* Check if the specified controller is actually accessible */
683
                r = controller_is_v1_accessible(NULL, controller);
×
684
                if (r < 0)
×
685
                        return r;
686
        }
687

688
        return cg_get_path(controller, path, suffix, ret);
23,972✔
689
}
690

691
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
8,072✔
692
        _cleanup_free_ char *fs = NULL;
8,072✔
693
        int r;
8,072✔
694

695
        assert(path);
8,072✔
696
        assert(name);
8,072✔
697
        assert(value || size <= 0);
8,072✔
698

699
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
8,072✔
700
        if (r < 0)
8,072✔
701
                return r;
702

703
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
8,072✔
704
}
705

706
int cg_get_xattr(const char *path, const char *name, void *value, size_t size) {
×
707
        _cleanup_free_ char *fs = NULL;
×
708
        ssize_t n;
×
709
        int r;
×
710

711
        assert(path);
×
712
        assert(name);
×
713

714
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
×
715
        if (r < 0)
×
716
                return r;
717

718
        n = getxattr(fs, name, value, size);
×
719
        if (n < 0)
×
720
                return -errno;
×
721

722
        return (int) n;
×
723
}
724

725
int cg_get_xattr_malloc(const char *path, const char *name, char **ret) {
19,761✔
726
        _cleanup_free_ char *fs = NULL;
19,761✔
727
        int r;
19,761✔
728

729
        assert(path);
19,761✔
730
        assert(name);
19,761✔
731

732
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
19,761✔
733
        if (r < 0)
19,761✔
734
                return r;
735

736
        return lgetxattr_malloc(fs, name, ret);
19,761✔
737
}
738

739
int cg_get_xattr_bool(const char *path, const char *name) {
423✔
740
        _cleanup_free_ char *fs = NULL;
423✔
741
        int r;
423✔
742

743
        assert(path);
423✔
744
        assert(name);
423✔
745

746
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
423✔
747
        if (r < 0)
423✔
748
                return r;
749

750
        return getxattr_at_bool(AT_FDCWD, fs, name, /* flags= */ 0);
423✔
751
}
752

753
int cg_remove_xattr(const char *path, const char *name) {
39,856✔
754
        _cleanup_free_ char *fs = NULL;
39,856✔
755
        int r;
39,856✔
756

757
        assert(path);
39,856✔
758
        assert(name);
39,856✔
759

760
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
39,856✔
761
        if (r < 0)
39,856✔
762
                return r;
763

764
        return RET_NERRNO(removexattr(fs, name));
79,712✔
765
}
766

767
int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
50,264✔
768
        _cleanup_fclose_ FILE *f = NULL;
50,264✔
769
        const char *fs, *controller_str = NULL;  /* avoid false maybe-uninitialized warning */
50,264✔
770
        int unified, r;
50,264✔
771

772
        assert(pid >= 0);
50,264✔
773
        assert(ret_path);
50,264✔
774

775
        if (controller) {
50,264✔
776
                if (!cg_controller_is_valid(controller))
49,928✔
777
                        return -EINVAL;
778
        } else
779
                controller = SYSTEMD_CGROUP_CONTROLLER;
780

781
        unified = cg_unified_controller(controller);
50,264✔
782
        if (unified < 0)
50,264✔
783
                return unified;
784
        if (unified == 0) {
50,264✔
785
                if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
×
786
                        controller_str = SYSTEMD_CGROUP_CONTROLLER_LEGACY;
787
                else
788
                        controller_str = controller;
×
789
        }
790

791
        fs = procfs_file_alloca(pid, "cgroup");
50,264✔
792
        r = fopen_unlocked(fs, "re", &f);
50,264✔
793
        if (r == -ENOENT)
50,264✔
794
                return -ESRCH;
795
        if (r < 0)
47,743✔
796
                return r;
797

798
        for (;;) {
48,912✔
799
                _cleanup_free_ char *line = NULL;
48,327✔
800
                char *e;
48,327✔
801

802
                r = read_line(f, LONG_LINE_MAX, &line);
48,327✔
803
                if (r < 0)
48,327✔
804
                        return r;
805
                if (r == 0)
48,325✔
806
                        return -ENODATA;
807

808
                if (unified) {
48,325✔
809
                        e = startswith(line, "0:");
48,325✔
810
                        if (!e)
48,325✔
811
                                continue;
585✔
812

813
                        e = strchr(e, ':');
47,740✔
814
                        if (!e)
47,740✔
815
                                continue;
×
816
                } else {
817
                        char *l;
×
818

819
                        l = strchr(line, ':');
×
820
                        if (!l)
×
821
                                continue;
×
822

823
                        l++;
×
824
                        e = strchr(l, ':');
×
825
                        if (!e)
×
826
                                continue;
×
827
                        *e = 0;
×
828

829
                        assert(controller_str);
×
830
                        r = string_contains_word(l, ",", controller_str);
×
831
                        if (r < 0)
×
832
                                return r;
833
                        if (r == 0)
×
834
                                continue;
×
835
                }
836

837
                _cleanup_free_ char *path = strdup(e + 1);
47,740✔
838
                if (!path)
47,740✔
839
                        return -ENOMEM;
840

841
                /* Refuse cgroup paths from outside our cgroup namespace */
842
                if (startswith(path, "/../"))
47,740✔
843
                        return -EUNATCH;
844

845
                /* Truncate suffix indicating the process is a zombie */
846
                e = endswith(path, " (deleted)");
47,740✔
847
                if (e)
47,740✔
848
                        *e = 0;
1,287✔
849

850
                *ret_path = TAKE_PTR(path);
47,740✔
851
                return 0;
47,740✔
852
        }
853
}
854

855
int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_path) {
20,930✔
856
        _cleanup_free_ char *path = NULL;
20,930✔
857
        int r;
20,930✔
858

859
        assert(ret_path);
20,930✔
860

861
        if (!pidref_is_set(pidref))
20,930✔
862
                return -ESRCH;
863
        if (pidref_is_remote(pidref))
41,860✔
864
                return -EREMOTE;
865

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

870
        r = cg_pid_get_path(controller, pidref->pid, &path);
20,930✔
871
        if (r < 0)
20,930✔
872
                return r;
873

874
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
875
        r = pidref_verify(pidref);
20,930✔
876
        if (r < 0)
20,930✔
877
                return r;
878

879
        *ret_path = TAKE_PTR(path);
20,930✔
880
        return 0;
20,930✔
881
}
882

883
int cg_is_empty(const char *controller, const char *path) {
4✔
884
        _cleanup_fclose_ FILE *f = NULL;
4✔
885
        pid_t pid;
4✔
886
        int r;
4✔
887

888
        assert(path);
4✔
889

890
        r = cg_enumerate_processes(controller, path, &f);
4✔
891
        if (r == -ENOENT)
4✔
892
                return true;
893
        if (r < 0)
4✔
894
                return r;
895

896
        r = cg_read_pid(f, &pid, CGROUP_DONT_SKIP_UNMAPPED);
4✔
897
        if (r < 0)
4✔
898
                return r;
899

900
        return r == 0;
4✔
901
}
902

903
int cg_is_empty_recursive(const char *controller, const char *path) {
12,350✔
904
        int r;
12,350✔
905

906
        assert(path);
12,350✔
907

908
        /* The root cgroup is always populated */
909
        if (controller && empty_or_root(path))
12,350✔
910
                return false;
911

912
        r = cg_unified_controller(controller);
12,350✔
913
        if (r < 0)
12,350✔
914
                return r;
915
        if (r > 0) {
12,350✔
916
                _cleanup_free_ char *t = NULL;
12,350✔
917

918
                /* On the unified hierarchy we can check empty state
919
                 * via the "populated" attribute of "cgroup.events". */
920

921
                r = cg_read_event(controller, path, "populated", &t);
12,350✔
922
                if (r == -ENOENT)
12,350✔
923
                        return true;
924
                if (r < 0)
4,757✔
925
                        return r;
926

927
                return streq(t, "0");
4,757✔
928
        } else {
929
                _cleanup_closedir_ DIR *d = NULL;
×
930
                char *fn;
×
931

932
                r = cg_is_empty(controller, path);
×
933
                if (r <= 0)
×
934
                        return r;
935

936
                r = cg_enumerate_subgroups(controller, path, &d);
×
937
                if (r == -ENOENT)
×
938
                        return true;
939
                if (r < 0)
×
940
                        return r;
941

942
                while ((r = cg_read_subgroup(d, &fn)) > 0) {
×
943
                        _cleanup_free_ char *p = NULL;
×
944

945
                        p = path_join(path, fn);
×
946
                        free(fn);
×
947
                        if (!p)
×
948
                                return -ENOMEM;
949

950
                        r = cg_is_empty_recursive(controller, p);
×
951
                        if (r <= 0)
×
952
                                return r;
953
                }
954
                if (r < 0)
×
955
                        return r;
956

957
                return true;
×
958
        }
959
}
960

961
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
23✔
962
        _cleanup_free_ char *controller = NULL, *path = NULL;
23✔
963
        int r;
23✔
964

965
        assert(spec);
23✔
966

967
        if (*spec == '/') {
23✔
968
                if (!path_is_normalized(spec))
15✔
969
                        return -EINVAL;
970

971
                if (ret_path) {
15✔
972
                        r = path_simplify_alloc(spec, &path);
15✔
973
                        if (r < 0)
15✔
974
                                return r;
975
                }
976

977
        } else {
978
                const char *e;
8✔
979

980
                e = strchr(spec, ':');
8✔
981
                if (e) {
8✔
982
                        controller = strndup(spec, e-spec);
6✔
983
                        if (!controller)
6✔
984
                                return -ENOMEM;
985
                        if (!cg_controller_is_valid(controller))
6✔
986
                                return -EINVAL;
987

988
                        if (!isempty(e + 1)) {
3✔
989
                                path = strdup(e+1);
2✔
990
                                if (!path)
2✔
991
                                        return -ENOMEM;
992

993
                                if (!path_is_normalized(path) ||
2✔
994
                                    !path_is_absolute(path))
2✔
995
                                        return -EINVAL;
996

997
                                path_simplify(path);
1✔
998
                        }
999

1000
                } else {
1001
                        if (!cg_controller_is_valid(spec))
2✔
1002
                                return -EINVAL;
1003

1004
                        if (ret_controller) {
1✔
1005
                                controller = strdup(spec);
1✔
1006
                                if (!controller)
1✔
1007
                                        return -ENOMEM;
1008
                        }
1009
                }
1010
        }
1011

1012
        if (ret_controller)
18✔
1013
                *ret_controller = TAKE_PTR(controller);
18✔
1014
        if (ret_path)
18✔
1015
                *ret_path = TAKE_PTR(path);
18✔
1016
        return 0;
1017
}
1018

1019
int cg_mangle_path(const char *path, char **ret) {
465✔
1020
        _cleanup_free_ char *c = NULL, *p = NULL;
465✔
1021
        int r;
465✔
1022

1023
        assert(path);
465✔
1024
        assert(ret);
465✔
1025

1026
        /* First, check if it already is a filesystem path */
1027
        if (path_startswith(path, "/sys/fs/cgroup"))
465✔
1028
                return path_simplify_alloc(path, ret);
461✔
1029

1030
        /* Otherwise, treat it as cg spec */
1031
        r = cg_split_spec(path, &c, &p);
4✔
1032
        if (r < 0)
4✔
1033
                return r;
1034

1035
        return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, ret);
8✔
1036
}
1037

1038
int cg_get_root_path(char **ret_path) {
14,131✔
1039
        char *p, *e;
14,131✔
1040
        int r;
14,131✔
1041

1042
        assert(ret_path);
14,131✔
1043

1044
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
14,131✔
1045
        if (r < 0)
14,131✔
1046
                return r;
14,131✔
1047

1048
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
14,131✔
1049
        if (!e)
14,131✔
1050
                e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); /* legacy */
79✔
1051
        if (!e)
79✔
1052
                e = endswith(p, "/system"); /* even more legacy */
79✔
1053
        if (e)
14,131✔
1054
                *e = 0;
14,052✔
1055

1056
        *ret_path = p;
14,131✔
1057
        return 0;
14,131✔
1058
}
1059

1060
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
11,053✔
1061
        _cleanup_free_ char *rt = NULL;
11,053✔
1062
        char *p;
11,053✔
1063
        int r;
11,053✔
1064

1065
        assert(cgroup);
11,053✔
1066
        assert(ret_shifted);
11,053✔
1067

1068
        if (!root) {
11,053✔
1069
                /* If the root was specified let's use that, otherwise
1070
                 * let's determine it from PID 1 */
1071

1072
                r = cg_get_root_path(&rt);
2,105✔
1073
                if (r < 0)
2,105✔
1074
                        return r;
1075

1076
                root = rt;
2,105✔
1077
        }
1078

1079
        p = path_startswith(cgroup, root);
11,053✔
1080
        if (p && p > cgroup)
11,053✔
1081
                *ret_shifted = p - 1;
2✔
1082
        else
1083
                *ret_shifted = cgroup;
11,051✔
1084

1085
        return 0;
1086
}
1087

1088
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
13,401✔
1089
        _cleanup_free_ char *raw = NULL;
13,401✔
1090
        const char *c;
13,401✔
1091
        int r;
13,401✔
1092

1093
        assert(pid >= 0);
13,401✔
1094
        assert(ret_cgroup);
13,401✔
1095

1096
        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw);
13,401✔
1097
        if (r < 0)
13,401✔
1098
                return r;
1099

1100
        r = cg_shift_path(raw, root, &c);
10,877✔
1101
        if (r < 0)
10,877✔
1102
                return r;
1103

1104
        if (c == raw) {
10,877✔
1105
                *ret_cgroup = TAKE_PTR(raw);
10,877✔
1106
                return 0;
10,877✔
1107
        }
1108

1109
        return strdup_to(ret_cgroup, c);
×
1110
}
1111

1112
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
32,451✔
1113
        assert(cgroup);
32,451✔
1114
        assert(ret_unit);
32,451✔
1115

1116
        size_t n = strcspn(cgroup, "/");
32,451✔
1117
        if (n < 3)
32,451✔
1118
                return -ENXIO;
1119

1120
        char *c = strndupa_safe(cgroup, n);
32,444✔
1121
        c = cg_unescape(c);
32,444✔
1122

1123
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
32,444✔
1124
                return -ENXIO;
1125

1126
        return strdup_to(ret_unit, c);
32,436✔
1127
}
1128

1129
static bool valid_slice_name(const char *p, size_t n) {
114,859✔
1130

1131
        if (!p)
114,859✔
1132
                return false;
1133

1134
        if (n < STRLEN("x.slice"))
114,844✔
1135
                return false;
1136

1137
        if (memcmp(p + n - 6, ".slice", 6) == 0) {
114,826✔
1138
                char buf[n+1], *c;
58,187✔
1139

1140
                memcpy(buf, p, n);
58,187✔
1141
                buf[n] = 0;
58,187✔
1142

1143
                c = cg_unescape(buf);
58,187✔
1144

1145
                return unit_name_is_valid(c, UNIT_NAME_PLAIN);
58,187✔
1146
        }
1147

1148
        return false;
1149
}
1150

1151
static const char *skip_slices(const char *p) {
40,785✔
1152
        assert(p);
40,785✔
1153

1154
        /* Skips over all slice assignments */
1155

1156
        for (;;) {
124,477✔
1157
                size_t n;
82,631✔
1158

1159
                p += strspn(p, "/");
82,631✔
1160

1161
                n = strcspn(p, "/");
82,631✔
1162
                if (!valid_slice_name(p, n))
82,631✔
1163
                        return p;
40,785✔
1164

1165
                p += n;
41,846✔
1166
        }
1167
}
1168

1169
int cg_path_get_unit(const char *path, char **ret) {
16,787✔
1170
        _cleanup_free_ char *unit = NULL;
16,787✔
1171
        const char *e;
16,787✔
1172
        int r;
16,787✔
1173

1174
        assert(path);
16,787✔
1175
        assert(ret);
16,787✔
1176

1177
        e = skip_slices(path);
16,787✔
1178

1179
        r = cg_path_decode_unit(e, &unit);
16,787✔
1180
        if (r < 0)
16,787✔
1181
                return r;
1182

1183
        /* We skipped over the slices, don't accept any now */
1184
        if (endswith(unit, ".slice"))
16,776✔
1185
                return -ENXIO;
1186

1187
        *ret = TAKE_PTR(unit);
16,776✔
1188
        return 0;
16,776✔
1189
}
1190

1191
int cg_path_get_unit_path(const char *path, char **ret) {
8,788✔
1192
        _cleanup_free_ char *path_copy = NULL;
8,788✔
1193
        char *unit_name;
8,788✔
1194

1195
        assert(path);
8,788✔
1196
        assert(ret);
8,788✔
1197

1198
        path_copy = strdup(path);
8,788✔
1199
        if (!path_copy)
8,788✔
1200
                return -ENOMEM;
1201

1202
        unit_name = (char *)skip_slices(path_copy);
8,788✔
1203
        unit_name[strcspn(unit_name, "/")] = 0;
8,788✔
1204

1205
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
8,788✔
1206
                return -ENXIO;
1207

1208
        *ret = TAKE_PTR(path_copy);
8,785✔
1209

1210
        return 0;
8,785✔
1211
}
1212

1213
int cg_pid_get_unit(pid_t pid, char **ret_unit) {
534✔
1214
        _cleanup_free_ char *cgroup = NULL;
534✔
1215
        int r;
534✔
1216

1217
        assert(ret_unit);
534✔
1218

1219
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
534✔
1220
        if (r < 0)
534✔
1221
                return r;
1222

1223
        return cg_path_get_unit(cgroup, ret_unit);
530✔
1224
}
1225

1226
int cg_pidref_get_unit(const PidRef *pidref, char **ret) {
392✔
1227
        _cleanup_free_ char *unit = NULL;
392✔
1228
        int r;
392✔
1229

1230
        assert(ret);
392✔
1231

1232
        if (!pidref_is_set(pidref))
392✔
1233
                return -ESRCH;
1234
        if (pidref_is_remote(pidref))
784✔
1235
                return -EREMOTE;
1236

1237
        r = cg_pid_get_unit(pidref->pid, &unit);
392✔
1238
        if (r < 0)
392✔
1239
                return r;
1240

1241
        r = pidref_verify(pidref);
388✔
1242
        if (r < 0)
388✔
1243
                return r;
1244

1245
        *ret = TAKE_PTR(unit);
388✔
1246
        return 0;
388✔
1247
}
1248

1249
/**
1250
 * Skip session-*.scope, but require it to be there.
1251
 */
1252
static const char *skip_session(const char *p) {
14,816✔
1253
        size_t n;
14,816✔
1254

1255
        if (isempty(p))
14,816✔
1256
                return NULL;
1257

1258
        p += strspn(p, "/");
14,812✔
1259

1260
        n = strcspn(p, "/");
14,812✔
1261
        if (n < STRLEN("session-x.scope"))
14,812✔
1262
                return NULL;
1263

1264
        if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) {
14,648✔
1265
                char buf[n - 8 - 6 + 1];
21✔
1266

1267
                memcpy(buf, p + 8, n - 8 - 6);
21✔
1268
                buf[n - 8 - 6] = 0;
21✔
1269

1270
                /* Note that session scopes never need unescaping,
1271
                 * since they cannot conflict with the kernel's own
1272
                 * names, hence we don't need to call cg_unescape()
1273
                 * here. */
1274

1275
                if (!session_id_valid(buf))
21✔
1276
                        return NULL;
21✔
1277

1278
                p += n;
21✔
1279
                p += strspn(p, "/");
21✔
1280
                return p;
21✔
1281
        }
1282

1283
        return NULL;
1284
}
1285

1286
/**
1287
 * Skip user@*.service, but require it to be there.
1288
 */
1289
static const char *skip_user_manager(const char *p) {
15,210✔
1290
        size_t n;
15,210✔
1291

1292
        if (isempty(p))
15,210✔
1293
                return NULL;
1294

1295
        p += strspn(p, "/");
15,206✔
1296

1297
        n = strcspn(p, "/");
15,206✔
1298
        if (n < STRLEN("user@x.service"))
15,206✔
1299
                return NULL;
1300

1301
        if (memcmp(p, "user@", 5) == 0 && memcmp(p + n - 8, ".service", 8) == 0) {
15,042✔
1302
                char buf[n - 5 - 8 + 1];
394✔
1303

1304
                memcpy(buf, p + 5, n - 5 - 8);
394✔
1305
                buf[n - 5 - 8] = 0;
394✔
1306

1307
                /* Note that user manager services never need unescaping,
1308
                 * since they cannot conflict with the kernel's own
1309
                 * names, hence we don't need to call cg_unescape()
1310
                 * here. */
1311

1312
                if (parse_uid(buf, NULL) < 0)
394✔
1313
                        return NULL;
394✔
1314

1315
                p += n;
394✔
1316
                p += strspn(p, "/");
394✔
1317

1318
                return p;
394✔
1319
        }
1320

1321
        return NULL;
1322
}
1323

1324
static const char *skip_user_prefix(const char *path) {
15,210✔
1325
        const char *e, *t;
15,210✔
1326

1327
        assert(path);
15,210✔
1328

1329
        /* Skip slices, if there are any */
1330
        e = skip_slices(path);
15,210✔
1331

1332
        /* Skip the user manager, if it's in the path now... */
1333
        t = skip_user_manager(e);
15,210✔
1334
        if (t)
15,210✔
1335
                return t;
1336

1337
        /* Alternatively skip the user session if it is in the path... */
1338
        return skip_session(e);
14,816✔
1339
}
1340

1341
int cg_path_get_user_unit(const char *path, char **ret) {
7,667✔
1342
        const char *t;
7,667✔
1343

1344
        assert(path);
7,667✔
1345
        assert(ret);
7,667✔
1346

1347
        t = skip_user_prefix(path);
7,667✔
1348
        if (!t)
7,667✔
1349
                return -ENXIO;
1350

1351
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
1352
         * parser. */
1353
        return cg_path_get_unit(t, ret);
215✔
1354
}
1355

1356
int cg_pid_get_user_unit(pid_t pid, char **ret_unit) {
124✔
1357
        _cleanup_free_ char *cgroup = NULL;
124✔
1358
        int r;
124✔
1359

1360
        assert(ret_unit);
124✔
1361

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

1366
        return cg_path_get_user_unit(cgroup, ret_unit);
124✔
1367
}
1368

1369
int cg_path_get_machine_name(const char *path, char **ret_machine) {
108✔
1370
        _cleanup_free_ char *u = NULL;
108✔
1371
        const char *sl;
108✔
1372
        int r;
108✔
1373

1374
        r = cg_path_get_unit(path, &u);
108✔
1375
        if (r < 0)
108✔
1376
                return r;
1377

1378
        sl = strjoina("/run/systemd/machines/unit:", u);
540✔
1379
        return readlink_malloc(sl, ret_machine);
108✔
1380
}
1381

1382
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
108✔
1383
        _cleanup_free_ char *cgroup = NULL;
108✔
1384
        int r;
108✔
1385

1386
        assert(ret_machine);
108✔
1387

1388
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
108✔
1389
        if (r < 0)
108✔
1390
                return r;
1391

1392
        return cg_path_get_machine_name(cgroup, ret_machine);
108✔
1393
}
1394

1395
int cg_path_get_session(const char *path, char **ret_session) {
8,336✔
1396
        _cleanup_free_ char *unit = NULL;
8,336✔
1397
        char *start, *end;
8,336✔
1398
        int r;
8,336✔
1399

1400
        assert(path);
8,336✔
1401

1402
        r = cg_path_get_unit(path, &unit);
8,336✔
1403
        if (r < 0)
8,336✔
1404
                return r;
1405

1406
        start = startswith(unit, "session-");
8,335✔
1407
        if (!start)
8,335✔
1408
                return -ENXIO;
1409
        end = endswith(start, ".scope");
188✔
1410
        if (!end)
188✔
1411
                return -ENXIO;
1412

1413
        *end = 0;
188✔
1414
        if (!session_id_valid(start))
188✔
1415
                return -ENXIO;
1416

1417
        if (!ret_session)
187✔
1418
                return 0;
1419

1420
        return strdup_to(ret_session, start);
187✔
1421
}
1422

1423
int cg_pid_get_session(pid_t pid, char **ret_session) {
730✔
1424
        _cleanup_free_ char *cgroup = NULL;
730✔
1425
        int r;
730✔
1426

1427
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
730✔
1428
        if (r < 0)
730✔
1429
                return r;
1430

1431
        return cg_path_get_session(cgroup, ret_session);
730✔
1432
}
1433

1434
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
292✔
1435
        int r;
292✔
1436

1437
        if (!pidref_is_set(pidref))
292✔
1438
                return -ESRCH;
292✔
1439
        if (pidref_is_remote(pidref))
584✔
1440
                return -EREMOTE;
1441

1442
        _cleanup_free_ char *session = NULL;
292✔
1443
        r = cg_pid_get_session(pidref->pid, &session);
292✔
1444
        if (r < 0)
292✔
1445
                return r;
1446

1447
        r = pidref_verify(pidref);
173✔
1448
        if (r < 0)
173✔
1449
                return r;
1450

1451
        if (ret)
173✔
1452
                *ret = TAKE_PTR(session);
173✔
1453
        return 0;
1454
}
1455

1456
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
8,020✔
1457
        _cleanup_free_ char *slice = NULL;
8,020✔
1458
        char *start, *end;
8,020✔
1459
        int r;
8,020✔
1460

1461
        assert(path);
8,020✔
1462

1463
        r = cg_path_get_slice(path, &slice);
8,020✔
1464
        if (r < 0)
8,020✔
1465
                return r;
1466

1467
        start = startswith(slice, "user-");
8,020✔
1468
        if (!start)
8,020✔
1469
                return -ENXIO;
1470

1471
        end = endswith(start, ".slice");
424✔
1472
        if (!end)
424✔
1473
                return -ENXIO;
1474

1475
        *end = 0;
424✔
1476
        if (parse_uid(start, ret_uid) < 0)
424✔
1477
                return -ENXIO;
×
1478

1479
        return 0;
1480
}
1481

1482
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
432✔
1483
        _cleanup_free_ char *cgroup = NULL;
432✔
1484
        int r;
432✔
1485

1486
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
432✔
1487
        if (r < 0)
432✔
1488
                return r;
1489

1490
        return cg_path_get_owner_uid(cgroup, ret_uid);
432✔
1491
}
1492

1493
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
52✔
1494
        int r;
52✔
1495

1496
        if (!pidref_is_set(pidref))
52✔
1497
                return -ESRCH;
52✔
1498
        if (pidref_is_remote(pidref))
52✔
1499
                return -EREMOTE;
1500

1501
        uid_t uid;
52✔
1502
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
52✔
1503
        if (r < 0)
52✔
1504
                return r;
1505

1506
        r = pidref_verify(pidref);
10✔
1507
        if (r < 0)
10✔
1508
                return r;
1509

1510
        if (ret)
10✔
1511
                *ret = uid;
10✔
1512

1513
        return 0;
1514
}
1515

1516
int cg_path_get_slice(const char *p, char **ret_slice) {
15,887✔
1517
        const char *e = NULL;
15,887✔
1518

1519
        assert(p);
15,887✔
1520
        assert(ret_slice);
15,887✔
1521

1522
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1523
         * the first non-slice unit. */
1524

1525
        for (;;) {
48,569✔
1526
                const char *s;
32,228✔
1527
                int n;
32,228✔
1528

1529
                n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s);
32,228✔
1530
                if (n < 0)
32,228✔
1531
                        return n;
×
1532
                if (!valid_slice_name(s, n))
32,228✔
1533
                        break;
1534

1535
                e = s;
16,341✔
1536
        }
1537

1538
        if (e)
15,887✔
1539
                return cg_path_decode_unit(e, ret_slice);
15,655✔
1540

1541
        return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
232✔
1542
}
1543

1544
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
128✔
1545
        _cleanup_free_ char *cgroup = NULL;
128✔
1546
        int r;
128✔
1547

1548
        assert(ret_slice);
128✔
1549

1550
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
128✔
1551
        if (r < 0)
128✔
1552
                return r;
1553

1554
        return cg_path_get_slice(cgroup, ret_slice);
128✔
1555
}
1556

1557
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,543✔
1558
        const char *t;
7,543✔
1559
        assert(p);
7,543✔
1560
        assert(ret_slice);
7,543✔
1561

1562
        t = skip_user_prefix(p);
7,543✔
1563
        if (!t)
7,543✔
1564
                return -ENXIO;
1565

1566
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1567
         * from here on. */
1568
        return cg_path_get_slice(t, ret_slice);
200✔
1569
}
1570

1571
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
×
1572
        _cleanup_free_ char *cgroup = NULL;
×
1573
        int r;
×
1574

1575
        assert(ret_slice);
×
1576

1577
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
×
1578
        if (r < 0)
×
1579
                return r;
1580

1581
        return cg_path_get_user_slice(cgroup, ret_slice);
×
1582
}
1583

1584
bool cg_needs_escape(const char *p) {
17,081✔
1585

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

1591
        if (!filename_is_valid(p))
17,081✔
1592
                return true;
1593

1594
        if (IN_SET(p[0], '_', '.'))
17,077✔
1595
                return true;
1596

1597
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
17,071✔
1598
                return true;
2✔
1599

1600
        if (startswith(p, "cgroup."))
17,069✔
1601
                return true;
1602

1603
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
238,938✔
1604
                const char *q;
221,871✔
1605

1606
                q = startswith(p, cgroup_controller_to_string(c));
221,871✔
1607
                if (!q)
221,871✔
1608
                        continue;
221,871✔
1609

1610
                if (q[0] == '.')
×
1611
                        return true;
1612
        }
1613

1614
        return false;
1615
}
1616

1617
int cg_escape(const char *p, char **ret) {
16,802✔
1618
        _cleanup_free_ char *n = NULL;
16,802✔
1619

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

1625
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1626

1627
        if (cg_needs_escape(p)) {
16,802✔
1628
                n = strjoin("_", p);
7✔
1629
                if (!n)
7✔
1630
                        return -ENOMEM;
1631

1632
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1633
                        return -EINVAL;
1634
        } else {
1635
                n = strdup(p);
16,795✔
1636
                if (!n)
16,795✔
1637
                        return -ENOMEM;
1638
        }
1639

1640
        *ret = TAKE_PTR(n);
16,802✔
1641
        return 0;
16,802✔
1642
}
1643

1644
char* cg_unescape(const char *p) {
99,642✔
1645
        assert(p);
99,642✔
1646

1647
        /* The return value of this function (unlike cg_escape())
1648
         * doesn't need free()! */
1649

1650
        if (p[0] == '_')
99,642✔
1651
                return (char*) p+1;
14✔
1652

1653
        return (char*) p;
1654
}
1655

1656
#define CONTROLLER_VALID                        \
1657
        DIGITS LETTERS                          \
1658
        "_"
1659

1660
bool cg_controller_is_valid(const char *p) {
399,901✔
1661
        const char *t, *s;
399,901✔
1662

1663
        if (!p)
399,901✔
1664
                return false;
1665

1666
        if (streq(p, SYSTEMD_CGROUP_CONTROLLER))
399,901✔
1667
                return true;
1668

1669
        s = startswith(p, "name=");
119,216✔
1670
        if (s)
119,216✔
1671
                p = s;
32✔
1672

1673
        if (IN_SET(*p, 0, '_'))
119,216✔
1674
                return false;
1675

1676
        for (t = p; *t; t++)
765,197✔
1677
                if (!strchr(CONTROLLER_VALID, *t))
645,992✔
1678
                        return false;
1679

1680
        if (t - p > NAME_MAX)
119,205✔
1681
                return false;
×
1682

1683
        return true;
1684
}
1685

1686
int cg_slice_to_path(const char *unit, char **ret) {
7,413✔
1687
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
7,413✔
1688
        const char *dash;
7,413✔
1689
        int r;
7,413✔
1690

1691
        assert(unit);
7,413✔
1692
        assert(ret);
7,413✔
1693

1694
        if (streq(unit, SPECIAL_ROOT_SLICE))
7,413✔
1695
                return strdup_to(ret, "");
7✔
1696

1697
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
7,406✔
1698
                return -EINVAL;
1699

1700
        if (!endswith(unit, ".slice"))
7,395✔
1701
                return -EINVAL;
1702

1703
        r = unit_name_to_prefix(unit, &p);
7,394✔
1704
        if (r < 0)
7,394✔
1705
                return r;
1706

1707
        dash = strchr(p, '-');
7,394✔
1708

1709
        /* Don't allow initial dashes */
1710
        if (dash == p)
7,394✔
1711
                return -EINVAL;
1712

1713
        while (dash) {
7,626✔
1714
                _cleanup_free_ char *escaped = NULL;
237✔
1715
                char n[dash - p + sizeof(".slice")];
237✔
1716

1717
#if HAS_FEATURE_MEMORY_SANITIZER
1718
                /* msan doesn't instrument stpncpy, so it thinks
1719
                 * n is later used uninitialized:
1720
                 * https://github.com/google/sanitizers/issues/926
1721
                 */
1722
                zero(n);
1723
#endif
1724

1725
                /* Don't allow trailing or double dashes */
1726
                if (IN_SET(dash[1], 0, '-'))
237✔
1727
                        return -EINVAL;
1728

1729
                strcpy(stpncpy(n, p, dash - p), ".slice");
235✔
1730
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
235✔
1731
                        return -EINVAL;
1732

1733
                r = cg_escape(n, &escaped);
235✔
1734
                if (r < 0)
235✔
1735
                        return r;
1736

1737
                if (!strextend(&s, escaped, "/"))
235✔
1738
                        return -ENOMEM;
1739

1740
                dash = strchr(dash+1, '-');
235✔
1741
        }
1742

1743
        r = cg_escape(unit, &e);
7,389✔
1744
        if (r < 0)
7,389✔
1745
                return r;
1746

1747
        if (!strextend(&s, e))
7,389✔
1748
                return -ENOMEM;
1749

1750
        *ret = TAKE_PTR(s);
7,389✔
1751
        return 0;
7,389✔
1752
}
1753

1754
int cg_is_threaded(const char *path) {
×
1755
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
1756
        _cleanup_strv_free_ char **v = NULL;
×
1757
        int r;
×
1758

1759
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.type", &fs);
×
1760
        if (r < 0)
×
1761
                return r;
1762

1763
        r = read_full_virtual_file(fs, &contents, NULL);
×
1764
        if (r == -ENOENT)
×
1765
                return false; /* Assume no. */
1766
        if (r < 0)
×
1767
                return r;
1768

1769
        v = strv_split(contents, NULL);
×
1770
        if (!v)
×
1771
                return -ENOMEM;
1772

1773
        /* If the cgroup is in the threaded mode, it contains "threaded".
1774
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
1775
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1776
}
1777

1778
int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) {
48,870✔
1779
        _cleanup_free_ char *p = NULL;
48,870✔
1780
        int r;
48,870✔
1781

1782
        r = cg_get_path(controller, path, attribute, &p);
48,870✔
1783
        if (r < 0)
48,870✔
1784
                return r;
1785

1786
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER);
48,870✔
1787
}
1788

1789
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) {
29,166✔
1790
        _cleanup_free_ char *p = NULL;
29,166✔
1791
        int r;
29,166✔
1792

1793
        r = cg_get_path(controller, path, attribute, &p);
29,166✔
1794
        if (r < 0)
29,166✔
1795
                return r;
1796

1797
        return read_one_line_file(p, ret);
29,166✔
1798
}
1799

1800
int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret) {
25,254✔
1801
        _cleanup_free_ char *value = NULL;
25,254✔
1802
        uint64_t v;
25,254✔
1803
        int r;
25,254✔
1804

1805
        assert(ret);
25,254✔
1806

1807
        r = cg_get_attribute(controller, path, attribute, &value);
25,254✔
1808
        if (r == -ENOENT)
25,254✔
1809
                return -ENODATA;
1810
        if (r < 0)
22,052✔
1811
                return r;
1812

1813
        if (streq(value, "max")) {
22,052✔
1814
                *ret = CGROUP_LIMIT_MAX;
4,653✔
1815
                return 0;
4,653✔
1816
        }
1817

1818
        r = safe_atou64(value, &v);
17,399✔
1819
        if (r < 0)
17,399✔
1820
                return r;
1821

1822
        *ret = v;
17,399✔
1823
        return 0;
17,399✔
1824
}
1825

1826
int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret) {
56✔
1827
        _cleanup_free_ char *value = NULL;
56✔
1828
        int r;
56✔
1829

1830
        assert(ret);
56✔
1831

1832
        r = cg_get_attribute(controller, path, attribute, &value);
56✔
1833
        if (r == -ENOENT)
56✔
1834
                return -ENODATA;
1835
        if (r < 0)
56✔
1836
                return r;
1837

1838
        r = parse_boolean(value);
56✔
1839
        if (r < 0)
56✔
1840
                return r;
1841

1842
        *ret = r;
56✔
1843
        return 0;
56✔
1844
}
1845

1846
int cg_get_owner(const char *path, uid_t *ret_uid) {
35✔
1847
        _cleanup_free_ char *f = NULL;
35✔
1848
        struct stat stats;
35✔
1849
        int r;
35✔
1850

1851
        assert(ret_uid);
35✔
1852

1853
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &f);
35✔
1854
        if (r < 0)
35✔
1855
                return r;
1856

1857
        if (stat(f, &stats) < 0)
35✔
1858
                return -errno;
16✔
1859

1860
        r = stat_verify_directory(&stats);
19✔
1861
        if (r < 0)
19✔
1862
                return r;
1863

1864
        *ret_uid = stats.st_uid;
19✔
1865
        return 0;
19✔
1866
}
1867

1868
int cg_get_keyed_attribute_full(
34,357✔
1869
                const char *controller,
1870
                const char *path,
1871
                const char *attribute,
1872
                char **keys,
1873
                char **ret_values,
1874
                CGroupKeyMode mode) {
1875

1876
        _cleanup_free_ char *filename = NULL, *contents = NULL;
34,357✔
1877
        const char *p;
34,357✔
1878
        size_t n, i, n_done = 0;
34,357✔
1879
        char **v;
34,357✔
1880
        int r;
34,357✔
1881

1882
        /* Reads one or more fields of a cgroup v2 keyed attribute file. The 'keys' parameter should be an strv with
1883
         * all keys to retrieve. The 'ret_values' parameter should be passed as string size with the same number of
1884
         * entries as 'keys'. On success each entry will be set to the value of the matching key.
1885
         *
1886
         * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. If mode
1887
         * is set to GG_KEY_MODE_GRACEFUL we ignore missing keys and return those that were parsed successfully. */
1888

1889
        r = cg_get_path(controller, path, attribute, &filename);
34,357✔
1890
        if (r < 0)
34,357✔
1891
                return r;
1892

1893
        r = read_full_file(filename, &contents, NULL);
34,357✔
1894
        if (r < 0)
34,357✔
1895
                return r;
1896

1897
        n = strv_length(keys);
28,111✔
1898
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
28,111✔
1899
                return 0;
1900

1901
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1902
        v = newa0(char*, n);
28,111✔
1903

1904
        for (p = contents; *p;) {
95,953✔
1905
                const char *w = NULL;
1906

1907
                for (i = 0; i < n; i++)
163,795✔
1908
                        if (!v[i]) {
105,773✔
1909
                                w = first_word(p, keys[i]);
95,953✔
1910
                                if (w)
95,953✔
1911
                                        break;
1912
                        }
1913

1914
                if (w) {
95,953✔
1915
                        size_t l;
37,931✔
1916

1917
                        l = strcspn(w, NEWLINE);
37,931✔
1918
                        v[i] = strndup(w, l);
37,931✔
1919
                        if (!v[i]) {
37,931✔
1920
                                r = -ENOMEM;
×
1921
                                goto fail;
×
1922
                        }
1923

1924
                        n_done++;
37,931✔
1925
                        if (n_done >= n)
37,931✔
1926
                                goto done;
28,111✔
1927

1928
                        p = w + l;
9,820✔
1929
                } else
1930
                        p += strcspn(p, NEWLINE);
58,022✔
1931

1932
                p += strspn(p, NEWLINE);
67,842✔
1933
        }
1934

1935
        if (mode & CG_KEY_MODE_GRACEFUL)
×
1936
                goto done;
×
1937

1938
        r = -ENXIO;
1939

1940
fail:
×
1941
        free_many_charp(v, n);
34,357✔
1942
        return r;
1943

1944
done:
28,111✔
1945
        memcpy(ret_values, v, sizeof(char*) * n);
28,111✔
1946
        if (mode & CG_KEY_MODE_GRACEFUL)
28,111✔
1947
                return n_done;
9,820✔
1948

1949
        return 0;
1950
}
1951

1952
int cg_mask_to_string(CGroupMask mask, char **ret) {
16,703✔
1953
        _cleanup_free_ char *s = NULL;
16,703✔
1954
        bool space = false;
16,703✔
1955
        CGroupController c;
16,703✔
1956
        size_t n = 0;
16,703✔
1957

1958
        assert(ret);
16,703✔
1959

1960
        if (mask == 0) {
16,703✔
1961
                *ret = NULL;
8,063✔
1962
                return 0;
8,063✔
1963
        }
1964

1965
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
120,960✔
1966
                const char *k;
112,320✔
1967
                size_t l;
112,320✔
1968

1969
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
112,320✔
1970
                        continue;
35,555✔
1971

1972
                k = cgroup_controller_to_string(c);
76,765✔
1973
                l = strlen(k);
76,765✔
1974

1975
                if (!GREEDY_REALLOC(s, n + space + l + 1))
76,765✔
1976
                        return -ENOMEM;
1977

1978
                if (space)
76,765✔
1979
                        s[n] = ' ';
68,125✔
1980
                memcpy(s + n + space, k, l);
76,765✔
1981
                n += space + l;
76,765✔
1982

1983
                space = true;
76,765✔
1984
        }
1985

1986
        assert(s);
8,640✔
1987

1988
        s[n] = 0;
8,640✔
1989
        *ret = TAKE_PTR(s);
8,640✔
1990

1991
        return 0;
8,640✔
1992
}
1993

1994
int cg_mask_from_string(const char *value, CGroupMask *ret) {
6,350✔
1995
        CGroupMask m = 0;
6,350✔
1996

1997
        assert(ret);
6,350✔
1998
        assert(value);
6,350✔
1999

2000
        for (;;) {
56,774✔
2001
                _cleanup_free_ char *n = NULL;
50,424✔
2002
                CGroupController v;
56,774✔
2003
                int r;
56,774✔
2004

2005
                r = extract_first_word(&value, &n, NULL, 0);
56,774✔
2006
                if (r < 0)
56,774✔
2007
                        return r;
×
2008
                if (r == 0)
56,774✔
2009
                        break;
2010

2011
                v = cgroup_controller_from_string(n);
50,424✔
2012
                if (v < 0)
50,424✔
2013
                        continue;
722✔
2014

2015
                m |= CGROUP_CONTROLLER_TO_MASK(v);
49,702✔
2016
        }
2017

2018
        *ret = m;
6,350✔
2019
        return 0;
6,350✔
2020
}
2021

2022
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
506✔
2023
        CGroupMask mask;
506✔
2024
        int r;
506✔
2025

2026
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
2027
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
2028
         * pseudo-controllers. */
2029

2030
        r = cg_all_unified();
506✔
2031
        if (r < 0)
506✔
2032
                return r;
506✔
2033
        if (r > 0) {
506✔
2034
                _cleanup_free_ char *controllers = NULL, *path = NULL;
506✔
2035

2036
                /* In the unified hierarchy we can read the supported and accessible controllers from
2037
                 * the top-level cgroup attribute */
2038

2039
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path);
506✔
2040
                if (r < 0)
506✔
2041
                        return r;
2042

2043
                r = read_one_line_file(path, &controllers);
506✔
2044
                if (r < 0)
506✔
2045
                        return r;
2046

2047
                r = cg_mask_from_string(controllers, &mask);
506✔
2048
                if (r < 0)
506✔
2049
                        return r;
2050

2051
                /* Mask controllers that are not supported in unified hierarchy. */
2052
                mask &= CGROUP_MASK_V2;
506✔
2053

2054
        } else {
2055
                CGroupController c;
×
2056

2057
                /* In the legacy hierarchy, we check which hierarchies are accessible. */
2058

2059
                mask = 0;
×
2060
                for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
×
2061
                        CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
×
2062
                        const char *n;
×
2063

2064
                        if (!FLAGS_SET(CGROUP_MASK_V1, bit))
×
2065
                                continue;
×
2066

2067
                        n = cgroup_controller_to_string(c);
×
2068
                        if (controller_is_v1_accessible(root, n) >= 0)
×
2069
                                mask |= bit;
×
2070
                }
2071
        }
2072

2073
        *ret = mask;
506✔
2074
        return 0;
506✔
2075
}
2076

2077
int cg_mask_supported(CGroupMask *ret) {
246✔
2078
        _cleanup_free_ char *root = NULL;
246✔
2079
        int r;
246✔
2080

2081
        r = cg_get_root_path(&root);
246✔
2082
        if (r < 0)
246✔
2083
                return r;
2084

2085
        return cg_mask_supported_subtree(root, ret);
246✔
2086
}
2087

2088
int cg_kernel_controllers(Set **ret) {
×
2089
        _cleanup_set_free_ Set *controllers = NULL;
×
2090
        _cleanup_fclose_ FILE *f = NULL;
×
2091
        int r;
×
2092

2093
        assert(ret);
×
2094

2095
        /* Determines the full list of kernel-known controllers. Might include controllers we don't actually support
2096
         * and controllers that aren't currently accessible (because not mounted). This does not include "name="
2097
         * pseudo-controllers. */
2098

2099
        r = fopen_unlocked("/proc/cgroups", "re", &f);
×
2100
        if (r == -ENOENT) {
×
2101
                *ret = NULL;
×
2102
                return 0;
×
2103
        }
2104
        if (r < 0)
×
2105
                return r;
2106

2107
        /* Ignore the header line */
2108
        (void) read_line(f, SIZE_MAX, NULL);
×
2109

2110
        for (;;) {
×
2111
                _cleanup_free_ char *controller = NULL;
×
2112
                int enabled = 0;
×
2113

2114
                if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) {
×
2115

2116
                        if (ferror(f))
×
2117
                                return -errno;
×
2118

2119
                        if (feof(f))
×
2120
                                break;
2121

2122
                        return -EBADMSG;
2123
                }
2124

2125
                if (!enabled)
×
2126
                        continue;
×
2127

2128
                if (!cg_controller_is_valid(controller))
×
2129
                        return -EBADMSG;
2130

2131
                r = set_ensure_consume(&controllers, &string_hash_ops_free, TAKE_PTR(controller));
×
2132
                if (r < 0)
×
2133
                        return r;
2134
        }
2135

2136
        *ret = TAKE_PTR(controllers);
×
2137

2138
        return 0;
×
2139
}
2140

2141
/* The hybrid mode was initially implemented in v232 and simply mounted cgroup2 on
2142
 * /sys/fs/cgroup/systemd. This unfortunately broke other tools (such as docker) which expected the v1
2143
 * "name=systemd" hierarchy on /sys/fs/cgroup/systemd. From v233 and on, the hybrid mode mounts v2 on
2144
 * /sys/fs/cgroup/unified and maintains "name=systemd" hierarchy on /sys/fs/cgroup/systemd for compatibility
2145
 * with other tools.
2146
 *
2147
 * To keep live upgrade working, we detect and support v232 layout. When v232 layout is detected, to keep
2148
 * cgroup v2 process management but disable the compat dual layout, we return true on
2149
 * cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) and false on cg_hybrid_unified().
2150
 */
2151
static thread_local bool unified_systemd_v232;
2152

2153
int cg_unified_cached(bool flush) {
658,878✔
2154
        static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN;
658,878✔
2155

2156
        struct statfs fs;
658,878✔
2157

2158
        /* Checks if we support the unified hierarchy. Returns an
2159
         * error when the cgroup hierarchies aren't mounted yet or we
2160
         * have any other trouble determining if the unified hierarchy
2161
         * is supported. */
2162

2163
        if (flush)
658,878✔
2164
                unified_cache = CGROUP_UNIFIED_UNKNOWN;
18,212✔
2165
        else if (unified_cache >= CGROUP_UNIFIED_NONE)
640,666✔
2166
                return unified_cache;
658,878✔
2167

2168
        if (statfs("/sys/fs/cgroup/", &fs) < 0)
33,016✔
2169
                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/\") failed: %m");
×
2170

2171
        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
33,016✔
2172
                log_debug("Found cgroup2 on /sys/fs/cgroup/, full unified hierarchy");
32,955✔
2173
                unified_cache = CGROUP_UNIFIED_ALL;
32,955✔
2174
        } else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) {
61✔
2175
                if (statfs("/sys/fs/cgroup/unified/", &fs) == 0 &&
×
2176
                    F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
×
2177
                        log_debug("Found cgroup2 on /sys/fs/cgroup/unified, unified hierarchy for systemd controller");
×
2178
                        unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
2179
                        unified_systemd_v232 = false;
×
2180
                } else {
2181
                        if (statfs("/sys/fs/cgroup/systemd/", &fs) < 0) {
×
2182
                                if (errno == ENOENT) {
×
2183
                                        /* Some other software may have set up /sys/fs/cgroup in a configuration we do not recognize. */
2184
                                        log_debug_errno(errno, "Unsupported cgroupsv1 setup detected: name=systemd hierarchy not found.");
×
2185
                                        return -ENOMEDIUM;
×
2186
                                }
2187
                                return log_debug_errno(errno, "statfs(\"/sys/fs/cgroup/systemd\" failed: %m");
×
2188
                        }
2189

2190
                        if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) {
×
2191
                                log_debug("Found cgroup2 on /sys/fs/cgroup/systemd, unified hierarchy for systemd controller (v232 variant)");
×
2192
                                unified_cache = CGROUP_UNIFIED_SYSTEMD;
×
2193
                                unified_systemd_v232 = true;
×
2194
                        } else if (F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) {
×
2195
                                log_debug("Found cgroup on /sys/fs/cgroup/systemd, legacy hierarchy");
×
2196
                                unified_cache = CGROUP_UNIFIED_NONE;
×
2197
                        } else {
2198
                                log_debug("Unexpected filesystem type %llx mounted on /sys/fs/cgroup/systemd, assuming legacy hierarchy",
×
2199
                                          (unsigned long long) fs.f_type);
2200
                                unified_cache = CGROUP_UNIFIED_NONE;
×
2201
                        }
2202
                }
2203
        } else if (F_TYPE_EQUAL(fs.f_type, SYSFS_MAGIC)) {
61✔
2204
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
61✔
2205
                                       "No filesystem is currently mounted on /sys/fs/cgroup.");
2206
        } else
2207
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
2208
                                       "Unknown filesystem type %llx mounted on /sys/fs/cgroup.",
2209
                                       (unsigned long long)fs.f_type);
2210

2211
        return unified_cache;
32,955✔
2212
}
2213

2214
int cg_unified_controller(const char *controller) {
102,225✔
2215
        int r;
102,225✔
2216

2217
        r = cg_unified_cached(false);
102,225✔
2218
        if (r < 0)
102,225✔
2219
                return r;
2220

2221
        if (r == CGROUP_UNIFIED_NONE)
102,225✔
2222
                return false;
2223

2224
        if (r >= CGROUP_UNIFIED_ALL)
102,225✔
2225
                return true;
2226

2227
        return streq_ptr(controller, SYSTEMD_CGROUP_CONTROLLER);
×
2228
}
2229

2230
int cg_all_unified(void) {
512,135✔
2231
        int r;
512,135✔
2232

2233
        r = cg_unified_cached(false);
512,135✔
2234
        if (r < 0)
512,135✔
2235
                return r;
2236

2237
        return r >= CGROUP_UNIFIED_ALL;
512,135✔
2238
}
2239

2240
int cg_hybrid_unified(void) {
26,305✔
2241
        int r;
26,305✔
2242

2243
        r = cg_unified_cached(false);
26,305✔
2244
        if (r < 0)
26,305✔
2245
                return r;
2246

2247
        return r == CGROUP_UNIFIED_SYSTEMD && !unified_systemd_v232;
26,305✔
2248
}
2249

2250
int cg_is_delegated(const char *path) {
19✔
2251
        int r;
19✔
2252

2253
        assert(path);
19✔
2254

2255
        r = cg_get_xattr_bool(path, "trusted.delegate");
19✔
2256
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
19✔
2257
                return r;
2258

2259
        /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the assumption
2260
         * that whoever is trusted enough to own the cgroup, is also trusted enough to decide if it is
2261
         * delegated or not this should be safe. */
2262
        r = cg_get_xattr_bool(path, "user.delegate");
6✔
2263
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
6✔
2264
}
2265

2266
int cg_is_delegated_fd(int fd) {
214✔
2267
        int r;
214✔
2268

2269
        assert(fd >= 0);
214✔
2270

2271
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* flags= */ 0);
214✔
2272
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
214✔
2273
                return r;
2274

2275
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* flags= */ 0);
200✔
2276
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
200✔
2277
}
2278

2279
int cg_has_coredump_receive(const char *path) {
2✔
2280
        int r;
2✔
2281

2282
        assert(path);
2✔
2283

2284
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
2285
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
2286
                return false;
×
2287

2288
        return r;
2289
}
2290

2291
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2292
        [CGROUP_IO_RBPS_MAX]    = CGROUP_LIMIT_MAX,
2293
        [CGROUP_IO_WBPS_MAX]    = CGROUP_LIMIT_MAX,
2294
        [CGROUP_IO_RIOPS_MAX]   = CGROUP_LIMIT_MAX,
2295
        [CGROUP_IO_WIOPS_MAX]   = CGROUP_LIMIT_MAX,
2296
};
2297

2298
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
2299
        [CGROUP_IO_RBPS_MAX]    = "IOReadBandwidthMax",
2300
        [CGROUP_IO_WBPS_MAX]    = "IOWriteBandwidthMax",
2301
        [CGROUP_IO_RIOPS_MAX]   = "IOReadIOPSMax",
2302
        [CGROUP_IO_WIOPS_MAX]   = "IOWriteIOPSMax",
2303
};
2304

2305
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
4,135✔
2306

2307
bool is_cgroup_fs(const struct statfs *s) {
26✔
2308
        return is_fs_type(s, CGROUP_SUPER_MAGIC) ||
28✔
2309
               is_fs_type(s, CGROUP2_SUPER_MAGIC);
2✔
2310
}
2311

2312
bool fd_is_cgroup_fs(int fd) {
1✔
2313
        struct statfs s;
1✔
2314

2315
        if (fstatfs(fd, &s) < 0)
1✔
2316
                return -errno;
×
2317

2318
        return is_cgroup_fs(&s);
1✔
2319
}
2320

2321
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
2322
        [CGROUP_CONTROLLER_CPU] = "cpu",
2323
        [CGROUP_CONTROLLER_CPUACCT] = "cpuacct",
2324
        [CGROUP_CONTROLLER_CPUSET] = "cpuset",
2325
        [CGROUP_CONTROLLER_IO] = "io",
2326
        [CGROUP_CONTROLLER_BLKIO] = "blkio",
2327
        [CGROUP_CONTROLLER_MEMORY] = "memory",
2328
        [CGROUP_CONTROLLER_DEVICES] = "devices",
2329
        [CGROUP_CONTROLLER_PIDS] = "pids",
2330
        [CGROUP_CONTROLLER_BPF_FIREWALL] = "bpf-firewall",
2331
        [CGROUP_CONTROLLER_BPF_DEVICES] = "bpf-devices",
2332
        [CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
2333
        [CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind",
2334
        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
2335
};
2336

2337
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
380,566✔
2338

2339
CGroupMask get_cpu_accounting_mask(void) {
1,648,619✔
2340
        static CGroupMask needed_mask = (CGroupMask) -1;
1,648,619✔
2341

2342
        /* On kernel ≥4.15 with unified hierarchy, cpu.stat's usage_usec is
2343
         * provided externally from the CPU controller, which means we don't
2344
         * need to enable the CPU controller just to get metrics. This is good,
2345
         * because enabling the CPU controller comes at a minor performance
2346
         * hit, especially when it's propagated deep into large hierarchies.
2347
         * There's also no separate CPU accounting controller available within
2348
         * a unified hierarchy.
2349
         *
2350
         * This combination of factors results in the desired cgroup mask to
2351
         * enable for CPU accounting varying as follows:
2352
         *
2353
         *                   ╔═════════════════════╤═════════════════════╗
2354
         *                   ║     Linux ≥4.15     │     Linux <4.15     ║
2355
         *   ╔═══════════════╬═════════════════════╪═════════════════════╣
2356
         *   ║ Unified       ║ nothing             │ CGROUP_MASK_CPU     ║
2357
         *   ╟───────────────╫─────────────────────┼─────────────────────╢
2358
         *   ║ Hybrid/Legacy ║ CGROUP_MASK_CPUACCT │ CGROUP_MASK_CPUACCT ║
2359
         *   ╚═══════════════╩═════════════════════╧═════════════════════╝
2360
         *
2361
         * We check kernel version here instead of manually checking whether
2362
         * cpu.stat is present for every cgroup, as that check in itself would
2363
         * already be fairly expensive.
2364
         *
2365
         * Kernels where this patch has been backported will therefore have the
2366
         * CPU controller enabled unnecessarily. This is more expensive than
2367
         * necessary, but harmless. ☺️
2368
         */
2369

2370
        if (needed_mask == (CGroupMask) -1) {
1,648,619✔
2371
                if (cg_all_unified()) {
620✔
2372
                        struct utsname u;
620✔
2373
                        assert_se(uname(&u) >= 0);
620✔
2374

2375
                        if (strverscmp_improved(u.release, "4.15") < 0)
620✔
2376
                                needed_mask = CGROUP_MASK_CPU;
×
2377
                        else
2378
                                needed_mask = 0;
620✔
2379
                } else
2380
                        needed_mask = CGROUP_MASK_CPUACCT;
×
2381
        }
2382

2383
        return needed_mask;
1,648,619✔
2384
}
2385

2386
bool cpu_accounting_is_cheap(void) {
2,221✔
2387
        return get_cpu_accounting_mask() == 0;
2,221✔
2388
}
2389

2390
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
2391
        [MANAGED_OOM_AUTO] = "auto",
2392
        [MANAGED_OOM_KILL] = "kill",
2393
};
2394

2395
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
37,689✔
2396

2397
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
2398
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
2399
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
2400
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
2401
};
2402

2403
DEFINE_STRING_TABLE_LOOKUP(managed_oom_preference, ManagedOOMPreference);
18,621✔
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