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

systemd / systemd / 21846209963

09 Feb 2026 03:52PM UTC coverage: 72.697% (-0.02%) from 72.716%
21846209963

push

github

daandemeyer
meson: guard symlinks in sysconfdir behind install_sysconfidr

Symlinks to files inside sysconfdir are now only installed if
ìnstall_sysconfdir=true (which is the default).

If sshconfdir,sshdconfdir,shellprofiledir are not inside sysconfdir and
install_sysconfidr=false, these symlinks are still installed to the
configured directory.

311951 of 429113 relevant lines covered (72.7%)

1156102.48 hits per line

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

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

3
#include <linux/fs.h>
4
#include <linux/magic.h>
5
#include <signal.h>
6
#include <stdlib.h>
7
#include <sys/xattr.h>
8
#include <threads.h>
9
#include <unistd.h>
10

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

38
int cg_is_available(void) {
23✔
39
        struct statfs fs;
23✔
40

41
        if (statfs("/sys/fs/cgroup/", &fs) < 0) {
23✔
42
                if (errno == ENOENT) /* sysfs not mounted? */
×
43
                        return false;
23✔
44

45
                return log_debug_errno(errno, "Failed to statfs /sys/fs/cgroup/: %m");
×
46
        }
47

48
        return is_fs_type(&fs, CGROUP2_SUPER_MAGIC);
23✔
49
}
50

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

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

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

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

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

70
                cgroupfs_fd = fsfd;
71
        }
72

73
        union {
12✔
74
                struct file_handle file_handle;
75
                uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
76
        } fh = {
12✔
77
                .file_handle.handle_bytes = sizeof(uint64_t),
78
                .file_handle.handle_type = FILEID_KERNFS,
79
        };
80

81
        unaligned_write_ne64(fh.file_handle.f_handle, id);
12✔
82

83
        return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
20✔
84
}
85

86
int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret) {
×
87
        _cleanup_close_ int cgfd = -EBADF;
×
88
        int r;
×
89

90
        cgfd = cg_cgroupid_open(cgroupfs_fd, id);
×
91
        if (cgfd < 0)
×
92
                return cgfd;
93

94
        _cleanup_free_ char *path = NULL;
×
95
        r = fd_get_path(cgfd, &path);
×
96
        if (r < 0)
×
97
                return r;
98

99
        if (!path_startswith(path, "/sys/fs/cgroup/"))
×
100
                return -EXDEV; /* recognizable error */
101

102
        if (ret)
×
103
                *ret = TAKE_PTR(path);
×
104
        return 0;
105
}
106

107
int cg_enumerate_processes(const char *path, FILE **ret) {
17,001✔
108
        _cleanup_free_ char *fs = NULL;
17,001✔
109
        FILE *f;
17,001✔
110
        int r;
17,001✔
111

112
        assert(ret);
17,001✔
113

114
        r = cg_get_path(path, "cgroup.procs", &fs);
17,001✔
115
        if (r < 0)
17,001✔
116
                return r;
117

118
        f = fopen(fs, "re");
17,001✔
119
        if (!f)
17,001✔
120
                return -errno;
10,635✔
121

122
        *ret = f;
6,366✔
123
        return 0;
6,366✔
124
}
125

126
int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags) {
10,674✔
127
        unsigned long ul;
10,674✔
128

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

131
        assert(f);
10,674✔
132
        assert(ret);
10,674✔
133

134
        /* NB: The kernel returns ENODEV if we tried to read from cgroup.procs of a cgroup that has been
135
         * removed already. Callers should handle that! */
136

137
        for (;;) {
10,674✔
138
                errno = 0;
10,674✔
139
                if (fscanf(f, "%lu", &ul) != 1) {
10,674✔
140

141
                        if (feof(f)) {
6,574✔
142
                                *ret = 0;
6,574✔
143
                                return 0;
6,574✔
144
                        }
145

146
                        return errno_or_else(EIO);
×
147
                }
148

149
                if (ul > PID_T_MAX)
4,100✔
150
                        return -EIO;
151

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

158
                *ret = (pid_t) ul;
4,100✔
159
                return 1;
4,100✔
160
        }
161
}
162

163
int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) {
7,725✔
164
        int r;
7,725✔
165

166
        assert(f);
7,725✔
167
        assert(ret);
7,725✔
168

169
        for (;;) {
×
170
                pid_t pid;
7,725✔
171

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

180
                if (pid == 0)
1,733✔
181
                        return -EREMOTE;
182

183
                r = pidref_set_pid(ret, pid);
1,733✔
184
                if (r >= 0)
1,733✔
185
                        return 1;
186
                if (r != -ESRCH)
×
187
                        return r;
188

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

193
bool cg_kill_supported(void) {
×
194
        static thread_local int supported = -1;
×
195

196
        if (supported >= 0)
×
197
                return supported;
×
198

199
        if (cg_is_available() <= 0)
×
200
                return (supported = false);
×
201

202
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
×
203
                return (supported = true);
×
204
        if (errno != ENOENT)
×
205
                log_debug_errno(errno, "Failed to check whether cgroup.kill is available, assuming not: %m");
×
206
        return (supported = false);
×
207
}
208

209
int cg_enumerate_subgroups(const char *path, DIR **ret) {
16,699✔
210
        _cleanup_free_ char *fs = NULL;
16,699✔
211
        DIR *d;
16,699✔
212
        int r;
16,699✔
213

214
        assert(ret);
16,699✔
215

216
        /* This is not recursive! */
217

218
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
16,699✔
219
        if (r < 0)
16,699✔
220
                return r;
221

222
        d = opendir(fs);
16,699✔
223
        if (!d)
16,699✔
224
                return -errno;
10,635✔
225

226
        *ret = d;
6,064✔
227
        return 0;
6,064✔
228
}
229

230
int cg_read_subgroup(DIR *d, char **ret) {
7,444✔
231
        assert(d);
7,444✔
232
        assert(ret);
7,444✔
233

234
        FOREACH_DIRENT_ALL(de, d, return -errno) {
293,018✔
235
                if (de->d_type != DT_DIR)
286,746✔
236
                        continue;
273,030✔
237

238
                if (dot_or_dot_dot(de->d_name))
13,716✔
239
                        continue;
12,544✔
240

241
                return strdup_to_full(ret, de->d_name);
1,172✔
242
        }
243

244
        *ret = NULL;
6,272✔
245
        return 0;
6,272✔
246
}
247

248
int cg_kill(
16,486✔
249
                const char *path,
250
                int sig,
251
                CGroupFlags flags,
252
                Set *killed_pids,
253
                cg_kill_log_func_t log_kill,
254
                void *userdata) {
255

256
        _cleanup_set_free_ Set *allocated_set = NULL;
16,486✔
257
        int r, ret = 0;
16,486✔
258

259
        assert(path);
16,486✔
260
        assert(sig >= 0);
16,486✔
261

262
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
263
          * don't send SIGCONT on SIGKILL. */
264
        if (IN_SET(sig, SIGCONT, SIGKILL))
16,486✔
265
                flags &= ~CGROUP_SIGCONT;
2,397✔
266

267
        /* This goes through the tasks list and kills them all. This is repeated until no further processes
268
         * are added to the tasks list, to properly handle forking processes.
269
         *
270
         * When sending SIGKILL, prefer cg_kill_kernel_sigkill(), which is fully atomic. */
271

272
        if (!killed_pids) {
16,486✔
273
                killed_pids = allocated_set = set_new(NULL);
575✔
274
                if (!killed_pids)
575✔
275
                        return -ENOMEM;
276
        }
277

278
        bool done;
16,584✔
279
        do {
16,584✔
280
                _cleanup_fclose_ FILE *f = NULL;
10,635✔
281
                int ret_log_kill;
16,584✔
282

283
                done = true;
16,584✔
284

285
                r = cg_enumerate_processes(path, &f);
16,584✔
286
                if (r == -ENOENT)
16,584✔
287
                        break;
288
                if (r < 0)
5,949✔
289
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
290

291
                for (;;) {
7,651✔
292
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
7,651✔
293

294
                        r = cg_read_pidref(f, &pidref, flags);
7,651✔
295
                        if (r == -ENODEV) {
7,651✔
296
                                /* reading from cgroup.pids will result in ENODEV if the cgroup is
297
                                 * concurrently removed. Just leave in that case, because a removed cgroup
298
                                 * contains no processes anymore. */
299
                                done = true;
300
                                break;
301
                        }
302
                        if (r < 0)
7,651✔
303
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
304
                        if (r == 0)
7,651✔
305
                                break;
306

307
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
1,702✔
308
                                continue;
576✔
309

310
                        if (set_contains(killed_pids, PID_TO_PTR(pidref.pid)))
1,126✔
311
                                continue;
753✔
312

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

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

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

330
                                if (ret == 0) {
373✔
331
                                        if (log_kill)
198✔
332
                                                ret = ret_log_kill;
333
                                        else
334
                                                ret = 1;
71✔
335
                                }
336
                        }
337

338
                        done = false;
373✔
339

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

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

348
        } while (!done);
5,949✔
349

350
        return ret;
351
}
352

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

361
        _cleanup_set_free_ Set *allocated_set = NULL;
×
362
        _cleanup_closedir_ DIR *d = NULL;
15,909✔
363
        int r, ret;
15,909✔
364

365
        assert(path);
15,909✔
366
        assert(sig >= 0);
15,909✔
367

368
        if (!killed_pids) {
15,909✔
369
                killed_pids = allocated_set = set_new(NULL);
15,382✔
370
                if (!killed_pids)
15,382✔
371
                        return -ENOMEM;
372
        }
373

374
        ret = cg_kill(path, sig, flags, killed_pids, log_kill, userdata);
15,909✔
375

376
        r = cg_enumerate_subgroups(path, &d);
15,909✔
377
        if (r < 0) {
15,909✔
378
                if (r != -ENOENT)
10,635✔
379
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
380

381
                return ret;
10,635✔
382
        }
383

384
        for (;;) {
5,538✔
385
                _cleanup_free_ char *fn = NULL, *p = NULL;
5,406✔
386

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

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

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

406
        return ret;
5,274✔
407
}
408

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

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

416
        assert(path);
×
417

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

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

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

429
        return 0;
430
}
431

432
int cg_get_path(const char *path, const char *suffix, char **ret) {
282,163✔
433
        char *t;
282,163✔
434

435
        assert(ret);
282,163✔
436

437
        if (isempty(path))
307,995✔
438
                path = TAKE_PTR(suffix);
439

440
        t = path_join("/sys/fs/cgroup", path, suffix);
282,163✔
441
        if (!t)
282,163✔
442
                return -ENOMEM;
443

444
        *ret = path_simplify(t);
282,163✔
445
        return 0;
282,163✔
446
}
447

448
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
7,250✔
449
        _cleanup_free_ char *fs = NULL;
7,250✔
450
        int r;
7,250✔
451

452
        assert(path);
7,250✔
453
        assert(name);
7,250✔
454
        assert(value || size <= 0);
7,250✔
455

456
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
7,250✔
457
        if (r < 0)
7,250✔
458
                return r;
459

460
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
7,250✔
461
}
462

463
int cg_get_xattr(const char *path, const char *name, char **ret, size_t *ret_size) {
17,302✔
464
        _cleanup_free_ char *fs = NULL;
17,302✔
465
        int r;
17,302✔
466

467
        assert(path);
17,302✔
468
        assert(name);
17,302✔
469

470
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
17,302✔
471
        if (r < 0)
17,302✔
472
                return r;
473

474
        return lgetxattr_malloc(fs, name, ret, ret_size);
17,302✔
475
}
476

477
int cg_get_xattr_bool(const char *path, const char *name) {
117✔
478
        _cleanup_free_ char *fs = NULL;
117✔
479
        int r;
117✔
480

481
        assert(path);
117✔
482
        assert(name);
117✔
483

484
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
117✔
485
        if (r < 0)
117✔
486
                return r;
487

488
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
117✔
489
}
490

491
int cg_remove_xattr(const char *path, const char *name) {
32,722✔
492
        _cleanup_free_ char *fs = NULL;
32,722✔
493
        int r;
32,722✔
494

495
        assert(path);
32,722✔
496
        assert(name);
32,722✔
497

498
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
32,722✔
499
        if (r < 0)
32,722✔
500
                return r;
501

502
        return RET_NERRNO(removexattr(fs, name));
65,444✔
503
}
504

505
int cg_pid_get_path(pid_t pid, char **ret_path) {
55,635✔
506
        _cleanup_fclose_ FILE *f = NULL;
55,635✔
507
        const char *fs;
55,635✔
508
        int r;
55,635✔
509

510
        assert(pid >= 0);
55,635✔
511
        assert(ret_path);
55,635✔
512

513
        fs = procfs_file_alloca(pid, "cgroup");
62,071✔
514
        r = fopen_unlocked(fs, "re", &f);
55,635✔
515
        if (r == -ENOENT)
55,635✔
516
                return -ESRCH;
517
        if (r < 0)
51,959✔
518
                return r;
519

520
        for (;;) {
51,959✔
521
                _cleanup_free_ char *line = NULL;
51,959✔
522
                char *e;
51,959✔
523

524
                r = read_line(f, LONG_LINE_MAX, &line);
51,959✔
525
                if (r < 0)
51,959✔
526
                        return r;
527
                if (r == 0)
51,958✔
528
                        return -ENODATA;
529

530
                e = startswith(line, "0:");
51,958✔
531
                if (!e)
51,958✔
532
                        continue;
×
533

534
                e = strchr(e, ':');
51,958✔
535
                if (!e)
51,958✔
536
                        continue;
×
537

538
                _cleanup_free_ char *path = strdup(e + 1);
51,958✔
539
                if (!path)
51,958✔
540
                        return -ENOMEM;
541

542
                /* Refuse cgroup paths from outside our cgroup namespace */
543
                if (startswith(path, "/../"))
51,958✔
544
                        return -EUNATCH;
545

546
                /* Truncate suffix indicating the process is a zombie */
547
                e = endswith(path, " (deleted)");
51,958✔
548
                if (e)
51,958✔
549
                        *e = 0;
141✔
550

551
                *ret_path = TAKE_PTR(path);
51,958✔
552
                return 0;
51,958✔
553
        }
554
}
555

556
int cg_pidref_get_path(const PidRef *pidref, char **ret_path) {
12,346✔
557
        _cleanup_free_ char *path = NULL;
12,346✔
558
        int r;
12,346✔
559

560
        assert(ret_path);
12,346✔
561

562
        if (!pidref_is_set(pidref))
12,346✔
563
                return -ESRCH;
564
        if (pidref_is_remote(pidref))
24,692✔
565
                return -EREMOTE;
566

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

571
        r = cg_pid_get_path(pidref->pid, &path);
12,346✔
572
        if (r < 0)
12,346✔
573
                return r;
574

575
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
576
        r = pidref_verify(pidref);
12,344✔
577
        if (r < 0)
12,344✔
578
                return r;
579

580
        *ret_path = TAKE_PTR(path);
12,344✔
581
        return 0;
12,344✔
582
}
583

584
int cg_is_empty(const char *path) {
2,852✔
585
        _cleanup_free_ char *t = NULL;
2,852✔
586
        int r;
2,852✔
587

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

591
        assert(path);
2,852✔
592

593
        /* The root cgroup is always populated */
594
        if (empty_or_root(path))
2,852✔
595
                return false;
596

597
        r = cg_get_keyed_attribute(path, "cgroup.events", STRV_MAKE("populated"), &t);
2,852✔
598
        if (r == -ENOENT)
2,852✔
599
                return true;
600
        if (r < 0)
295✔
601
                return r;
602

603
        return streq(t, "0");
295✔
604
}
605

606
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
19✔
607
        _cleanup_free_ char *controller = NULL;
19✔
608
        const char *path;
19✔
609
        int r;
19✔
610

611
        assert(spec);
19✔
612

613
        /* This extracts the path part from the deprecated controller:path spec. The path must be absolute or
614
         * an empty string. No validation is done for the controller part. */
615

616
        if (isempty(spec) || path_is_absolute(spec)) {
19✔
617
                /* Assume this does not contain controller. */
618
                path = spec;
12✔
619
                goto finalize;
12✔
620
        }
621

622
        const char *e = strchr(spec, ':');
7✔
623
        if (!e) {
7✔
624
                /* Controller only. */
625
                if (ret_controller) {
1✔
626
                        controller = strdup(spec);
1✔
627
                        if (!controller)
1✔
628
                                return -ENOMEM;
629
                }
630

631
                path = NULL;
632
        } else {
633
                /* Both controller and path. */
634
                if (ret_controller) {
6✔
635
                        controller = strndup(spec, e - spec);
6✔
636
                        if (!controller)
6✔
637
                                return -ENOMEM;
638
                }
639

640
                path = e + 1;
6✔
641
        }
642

643
finalize:
12✔
644
        path = empty_to_null(path);
18✔
645

646
        if (path) {
15✔
647
                /* Non-empty path must be absolute. */
648
                if (!path_is_absolute(path))
15✔
649
                        return -EINVAL;
650

651
                /* Path must not contain dot-dot. */
652
                if (!path_is_safe(path))
14✔
653
                        return -EINVAL;
654
        }
655

656
        if (ret_path) {
18✔
657
                r = path_simplify_alloc(path, ret_path);
18✔
658
                if (r < 0)
18✔
659
                        return r;
660
        }
661

662
        if (ret_controller)
18✔
663
                *ret_controller = TAKE_PTR(controller);
18✔
664

665
        return 0;
666
}
667

668
int cg_get_root_path(char **ret_path) {
25,273✔
669
        char *p, *e;
25,273✔
670
        int r;
25,273✔
671

672
        assert(ret_path);
25,273✔
673

674
        r = cg_pid_get_path(1, &p);
25,273✔
675
        if (r < 0)
25,273✔
676
                return r;
25,273✔
677

678
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
25,273✔
679
        if (e)
25,273✔
680
                *e = 0;
25,240✔
681

682
        *ret_path = p;
25,273✔
683
        return 0;
25,273✔
684
}
685

686
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
12,906✔
687
        int r;
12,906✔
688

689
        assert(cgroup);
12,906✔
690
        assert(ret_shifted);
12,906✔
691

692
        _cleanup_free_ char *rt = NULL;
12,906✔
693
        if (!root) {
12,906✔
694
                /* If the root was specified let's use that, otherwise
695
                 * let's determine it from PID 1 */
696

697
                r = cg_get_root_path(&rt);
3,313✔
698
                if (r < 0)
3,313✔
699
                        return r;
700

701
                root = rt;
3,313✔
702
        }
703

704
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
12,906✔
705
        return 0;
12,906✔
706
}
707

708
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
16,403✔
709
        _cleanup_free_ char *raw = NULL;
16,403✔
710
        const char *c;
16,403✔
711
        int r;
16,403✔
712

713
        assert(pid >= 0);
16,403✔
714
        assert(ret_cgroup);
16,403✔
715

716
        r = cg_pid_get_path(pid, &raw);
16,403✔
717
        if (r < 0)
16,403✔
718
                return r;
719

720
        r = cg_shift_path(raw, root, &c);
12,728✔
721
        if (r < 0)
12,728✔
722
                return r;
723

724
        if (c == raw) {
12,728✔
725
                *ret_cgroup = TAKE_PTR(raw);
12,728✔
726
                return 0;
12,728✔
727
        }
728

729
        return strdup_to(ret_cgroup, c);
×
730
}
731

732
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
33,722✔
733
        assert(cgroup);
33,722✔
734

735
        size_t n = strcspn(cgroup, "/");
33,722✔
736
        if (n < 3)
33,722✔
737
                return -ENXIO;
738

739
        char *c = strndupa_safe(cgroup, n);
33,711✔
740
        c = cg_unescape(c);
33,711✔
741

742
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
33,711✔
743
                return -ENXIO;
744

745
        if (ret_unit)
33,702✔
746
                return strdup_to(ret_unit, c);
33,702✔
747

748
        return 0;
749
}
750

751
static bool valid_slice_name(const char *p, size_t n) {
119,544✔
752
        assert(p || n == 0);
119,544✔
753

754
        if (n < STRLEN("x.slice"))
119,544✔
755
                return false;
756

757
        char *c = strndupa_safe(p, n);
119,505✔
758
        if (!endswith(c, ".slice"))
119,505✔
759
                return false;
760

761
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
61,032✔
762
}
763

764
static const char* skip_slices(const char *p) {
42,459✔
765
        assert(p);
42,459✔
766

767
        /* Skips over all slice assignments */
768

769
        for (;;) {
130,883✔
770
                size_t n;
86,671✔
771

772
                p += strspn(p, "/");
86,671✔
773

774
                n = strcspn(p, "/");
86,671✔
775
                if (!valid_slice_name(p, n))
86,671✔
776
                        return p;
42,459✔
777

778
                p += n;
44,212✔
779
        }
780
}
781

782
int cg_path_get_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
17,887✔
783
        int r;
17,887✔
784

785
        assert(path);
17,887✔
786

787
        const char *e = skip_slices(path);
17,887✔
788

789
        _cleanup_free_ char *unit = NULL;
17,887✔
790
        r = cg_path_decode_unit(e, &unit);
17,887✔
791
        if (r < 0)
17,887✔
792
                return r;
793

794
        /* We skipped over the slices, don't accept any now */
795
        if (endswith(unit, ".slice"))
17,871✔
796
                return -ENXIO;
797

798
        if (ret_subgroup) {
17,871✔
799
                _cleanup_free_ char *subgroup = NULL;
×
800
                e += strcspn(e, "/");
713✔
801
                e += strspn(e, "/");
713✔
802

803
                if (isempty(e))
713✔
804
                        subgroup = NULL;
805
                else {
806
                        subgroup = strdup(e);
240✔
807
                        if (!subgroup)
240✔
808
                                return -ENOMEM;
×
809
                }
810

811
                path_simplify(subgroup);
713✔
812

813
                *ret_subgroup = TAKE_PTR(subgroup);
713✔
814
        }
815

816
        if (ret_unit)
17,871✔
817
                *ret_unit = TAKE_PTR(unit);
17,871✔
818

819
        return 0;
820
}
821

822
int cg_path_get_unit_path(const char *path, char **ret) {
9,431✔
823
        _cleanup_free_ char *path_copy = NULL;
9,431✔
824
        char *unit_name;
9,431✔
825

826
        assert(path);
9,431✔
827
        assert(ret);
9,431✔
828

829
        path_copy = strdup(path);
9,431✔
830
        if (!path_copy)
9,431✔
831
                return -ENOMEM;
832

833
        unit_name = (char*) skip_slices(path_copy);
9,431✔
834
        unit_name[strcspn(unit_name, "/")] = 0;
9,431✔
835

836
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,431✔
837
                return -ENXIO;
838

839
        *ret = TAKE_PTR(path_copy);
9,428✔
840

841
        return 0;
9,428✔
842
}
843

844
int cg_pid_get_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
776✔
845
        int r;
776✔
846

847
        _cleanup_free_ char *cgroup = NULL;
776✔
848
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
776✔
849
        if (r < 0)
776✔
850
                return r;
851

852
        return cg_path_get_unit_full(cgroup, ret_unit, ret_subgroup);
776✔
853
}
854

855
int cg_pidref_get_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
691✔
856
        int r;
691✔
857

858
        if (!pidref_is_set(pidref))
691✔
859
                return -ESRCH;
691✔
860
        if (pidref_is_remote(pidref))
1,382✔
861
                return -EREMOTE;
862

863
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
691✔
864
        r = cg_pid_get_unit_full(pidref->pid, &unit, &subgroup);
691✔
865
        if (r < 0)
691✔
866
                return r;
867

868
        r = pidref_verify(pidref);
691✔
869
        if (r < 0)
691✔
870
                return r;
871

872
        if (ret_unit)
691✔
873
                *ret_unit = TAKE_PTR(unit);
691✔
874
        if (ret_subgroup)
691✔
875
                *ret_subgroup = TAKE_PTR(subgroup);
49✔
876
        return 0;
877
}
878

879
static const char* skip_session(const char *p) {
14,754✔
880
        size_t n;
14,754✔
881

882
        /* Skip session-*.scope, but require it to be there. */
883

884
        if (isempty(p))
14,754✔
885
                return NULL;
886

887
        p += strspn(p, "/");
14,750✔
888

889
        n = strcspn(p, "/");
14,750✔
890
        if (n < STRLEN("session-x.scope"))
14,750✔
891
                return NULL;
892

893
        const char *s = startswith(p, "session-");
14,590✔
894
        if (!s)
14,590✔
895
                return NULL;
896

897
        /* Note that session scopes never need unescaping, since they cannot conflict with the kernel's
898
         * own names, hence we don't need to call cg_unescape() here. */
899
        char *f = strndupa_safe(s, p + n - s),
25✔
900
             *e = endswith(f, ".scope");
25✔
901
        if (!e)
25✔
902
                return NULL;
903
        *e = '\0';
25✔
904

905
        if (!session_id_valid(f))
25✔
906
                return NULL;
907

908
        return skip_leading_slash(p + n);
25✔
909
}
910

911
static const char* skip_user_manager(const char *p) {
15,141✔
912
        size_t n;
15,141✔
913

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

916
        if (isempty(p))
15,141✔
917
                return NULL;
15,141✔
918

919
        p += strspn(p, "/");
15,137✔
920

921
        n = strcspn(p, "/");
15,137✔
922
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,137✔
923
                return NULL;
924

925
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
926
         * resolve user/capsule name. */
927
        _cleanup_free_ char *unit_name = strndup(p, n);
14,979✔
928
        if (!unit_name)
14,979✔
929
                return NULL;
930

931
        _cleanup_free_ char *i = NULL;
14,979✔
932
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
14,979✔
933

934
        if (type != UNIT_NAME_INSTANCE)
14,979✔
935
                return NULL;
936

937
        /* Note that user manager services never need unescaping, since they cannot conflict with the
938
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
939
         * instance names, they should be always valid as we validate them upon unit start. */
940
        if (!(startswith(unit_name, "user@") && parse_uid(i, NULL) >= 0) &&
528✔
941
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
78✔
942
                return NULL;
68✔
943

944
        return skip_leading_slash(p + n);
387✔
945
}
946

947
static const char* skip_user_prefix(const char *path) {
15,141✔
948
        const char *e, *t;
15,141✔
949

950
        assert(path);
15,141✔
951

952
        /* Skip slices, if there are any */
953
        e = skip_slices(path);
15,141✔
954

955
        /* Skip the user manager, if it's in the path now... */
956
        t = skip_user_manager(e);
15,141✔
957
        if (t)
15,141✔
958
                return t;
959

960
        /* Alternatively skip the user session if it is in the path... */
961
        return skip_session(e);
14,754✔
962
}
963

964
int cg_path_get_user_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
7,599✔
965
        const char *t;
7,599✔
966

967
        assert(path);
7,599✔
968

969
        t = skip_user_prefix(path);
7,599✔
970
        if (!t)
7,599✔
971
                return -ENXIO;
972

973
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
974
         * parser. */
975
        return cg_path_get_unit_full(t, ret_unit, ret_subgroup);
216✔
976
}
977

978
int cg_pid_get_user_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
58✔
979
        int r;
58✔
980

981
        _cleanup_free_ char *cgroup = NULL;
58✔
982
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
58✔
983
        if (r < 0)
58✔
984
                return r;
985

986
        return cg_path_get_user_unit_full(cgroup, ret_unit, ret_subgroup);
58✔
987
}
988

989
int cg_pidref_get_user_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
17✔
990
        int r;
17✔
991

992
        if (!pidref_is_set(pidref))
17✔
993
                return -ESRCH;
17✔
994
        if (pidref_is_remote(pidref))
34✔
995
                return -EREMOTE;
996

997
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
17✔
998
        r = cg_pid_get_user_unit_full(pidref->pid, &unit, &subgroup);
17✔
999
        if (r < 0)
17✔
1000
                return r;
1001

1002
        r = pidref_verify(pidref);
8✔
1003
        if (r < 0)
8✔
1004
                return r;
1005

1006
        if (ret_unit)
8✔
1007
                *ret_unit = TAKE_PTR(unit);
8✔
1008
        if (ret_subgroup)
8✔
1009
                *ret_subgroup = TAKE_PTR(subgroup);
4✔
1010
        return 0;
1011
}
1012

1013
int cg_path_get_machine_name(const char *path, char **ret_machine) {
39✔
1014
        _cleanup_free_ char *u = NULL;
39✔
1015
        const char *sl;
39✔
1016
        int r;
39✔
1017

1018
        r = cg_path_get_unit(path, &u);
39✔
1019
        if (r < 0)
39✔
1020
                return r;
1021

1022
        sl = strjoina("/run/systemd/machines/unit:", u);
195✔
1023
        return readlink_malloc(sl, ret_machine);
39✔
1024
}
1025

1026
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
39✔
1027
        _cleanup_free_ char *cgroup = NULL;
39✔
1028
        int r;
39✔
1029

1030
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
39✔
1031
        if (r < 0)
39✔
1032
                return r;
1033

1034
        return cg_path_get_machine_name(cgroup, ret_machine);
39✔
1035
}
1036

1037
int cg_path_get_session(const char *path, char **ret_session) {
9,246✔
1038
        _cleanup_free_ char *unit = NULL;
9,246✔
1039
        char *start, *end;
9,246✔
1040
        int r;
9,246✔
1041

1042
        assert(path);
9,246✔
1043

1044
        r = cg_path_get_unit(path, &unit);
9,246✔
1045
        if (r < 0)
9,246✔
1046
                return r;
1047

1048
        start = startswith(unit, "session-");
9,245✔
1049
        if (!start)
9,245✔
1050
                return -ENXIO;
1051
        end = endswith(start, ".scope");
444✔
1052
        if (!end)
444✔
1053
                return -ENXIO;
1054

1055
        *end = 0;
444✔
1056
        if (!session_id_valid(start))
444✔
1057
                return -ENXIO;
1058

1059
        if (!ret_session)
443✔
1060
                return 0;
1061

1062
        return strdup_to(ret_session, start);
443✔
1063
}
1064

1065
int cg_pid_get_session(pid_t pid, char **ret_session) {
1,644✔
1066
        _cleanup_free_ char *cgroup = NULL;
1,644✔
1067
        int r;
1,644✔
1068

1069
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1,644✔
1070
        if (r < 0)
1,644✔
1071
                return r;
1072

1073
        return cg_path_get_session(cgroup, ret_session);
1,644✔
1074
}
1075

1076
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
441✔
1077
        int r;
441✔
1078

1079
        if (!pidref_is_set(pidref))
441✔
1080
                return -ESRCH;
441✔
1081
        if (pidref_is_remote(pidref))
882✔
1082
                return -EREMOTE;
1083

1084
        _cleanup_free_ char *session = NULL;
441✔
1085
        r = cg_pid_get_session(pidref->pid, &session);
441✔
1086
        if (r < 0)
441✔
1087
                return r;
1088

1089
        r = pidref_verify(pidref);
390✔
1090
        if (r < 0)
390✔
1091
                return r;
1092

1093
        if (ret)
390✔
1094
                *ret = TAKE_PTR(session);
390✔
1095
        return 0;
1096
}
1097

1098
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
8,264✔
1099
        _cleanup_free_ char *slice = NULL;
8,264✔
1100
        char *start, *end;
8,264✔
1101
        int r;
8,264✔
1102

1103
        assert(path);
8,264✔
1104

1105
        r = cg_path_get_slice(path, &slice);
8,264✔
1106
        if (r < 0)
8,264✔
1107
                return r;
1108

1109
        start = startswith(slice, "user-");
8,264✔
1110
        if (!start)
8,264✔
1111
                return -ENXIO;
1112

1113
        end = endswith(start, ".slice");
748✔
1114
        if (!end)
748✔
1115
                return -ENXIO;
1116

1117
        *end = 0;
748✔
1118
        if (parse_uid(start, ret_uid) < 0)
748✔
1119
                return -ENXIO;
×
1120

1121
        return 0;
1122
}
1123

1124
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
685✔
1125
        _cleanup_free_ char *cgroup = NULL;
685✔
1126
        int r;
685✔
1127

1128
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
685✔
1129
        if (r < 0)
685✔
1130
                return r;
1131

1132
        return cg_path_get_owner_uid(cgroup, ret_uid);
685✔
1133
}
1134

1135
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
51✔
1136
        int r;
51✔
1137

1138
        if (!pidref_is_set(pidref))
51✔
1139
                return -ESRCH;
51✔
1140
        if (pidref_is_remote(pidref))
51✔
1141
                return -EREMOTE;
1142

1143
        uid_t uid;
51✔
1144
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
51✔
1145
        if (r < 0)
51✔
1146
                return r;
1147

1148
        r = pidref_verify(pidref);
12✔
1149
        if (r < 0)
12✔
1150
                return r;
1151

1152
        if (ret)
12✔
1153
                *ret = uid;
12✔
1154

1155
        return 0;
1156
}
1157

1158
int cg_path_get_slice(const char *p, char **ret_slice) {
16,053✔
1159
        const char *e = NULL;
16,053✔
1160

1161
        assert(p);
16,053✔
1162

1163
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1164
         * the first non-slice unit. */
1165

1166
        for (;;) {
49,693✔
1167
                const char *s;
32,873✔
1168
                int n;
32,873✔
1169

1170
                n = path_find_first_component(&p, /* accept_dot_dot= */ false, &s);
32,873✔
1171
                if (n < 0)
32,873✔
1172
                        return n;
×
1173
                if (!valid_slice_name(s, n))
32,873✔
1174
                        break;
1175

1176
                e = s;
16,820✔
1177
        }
1178

1179
        if (e)
16,053✔
1180
                return cg_path_decode_unit(e, ret_slice);
15,826✔
1181

1182
        if (ret_slice)
227✔
1183
                return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
227✔
1184

1185
        return 0;
1186
}
1187

1188
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
58✔
1189
        _cleanup_free_ char *cgroup = NULL;
58✔
1190
        int r;
58✔
1191

1192
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
58✔
1193
        if (r < 0)
58✔
1194
                return r;
1195

1196
        return cg_path_get_slice(cgroup, ret_slice);
58✔
1197
}
1198

1199
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,542✔
1200
        const char *t;
7,542✔
1201
        assert(p);
7,542✔
1202

1203
        t = skip_user_prefix(p);
7,542✔
1204
        if (!t)
7,542✔
1205
                return -ENXIO;
1206

1207
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1208
         * from here on. */
1209
        return cg_path_get_slice(t, ret_slice);
196✔
1210
}
1211

1212
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1213
        _cleanup_free_ char *cgroup = NULL;
1✔
1214
        int r;
1✔
1215

1216
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1✔
1217
        if (r < 0)
1✔
1218
                return r;
1219

1220
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1221
}
1222

1223
bool cg_needs_escape(const char *p) {
25,913✔
1224

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

1230
        if (!filename_is_valid(p))
25,913✔
1231
                return true;
1232

1233
        if (IN_SET(p[0], '_', '.'))
25,909✔
1234
                return true;
1235

1236
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
25,903✔
1237
                return true;
2✔
1238

1239
        if (startswith(p, "cgroup."))
25,901✔
1240
                return true;
1241

1242
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
388,485✔
1243
                const char *q;
362,586✔
1244

1245
                q = startswith(p, cgroup_controller_to_string(c));
362,586✔
1246
                if (!q)
362,586✔
1247
                        continue;
362,586✔
1248

1249
                if (q[0] == '.')
×
1250
                        return true;
1251
        }
1252

1253
        return false;
1254
}
1255

1256
int cg_escape(const char *p, char **ret) {
25,657✔
1257
        _cleanup_free_ char *n = NULL;
25,657✔
1258

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

1264
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1265

1266
        if (cg_needs_escape(p)) {
25,657✔
1267
                n = strjoin("_", p);
7✔
1268
                if (!n)
7✔
1269
                        return -ENOMEM;
1270

1271
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1272
                        return -EINVAL;
1273
        } else {
1274
                n = strdup(p);
25,650✔
1275
                if (!n)
25,650✔
1276
                        return -ENOMEM;
1277
        }
1278

1279
        *ret = TAKE_PTR(n);
25,657✔
1280
        return 0;
25,657✔
1281
}
1282

1283
char* cg_unescape(const char *p) {
104,375✔
1284
        assert(p);
104,375✔
1285

1286
        /* The return value of this function (unlike cg_escape())
1287
         * doesn't need free()! */
1288

1289
        if (p[0] == '_')
104,375✔
1290
                return (char*) p+1;
14✔
1291

1292
        return (char*) p;
1293
}
1294

1295
int cg_slice_to_path(const char *unit, char **ret) {
11,139✔
1296
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
11,139✔
1297
        const char *dash;
11,139✔
1298
        int r;
11,139✔
1299

1300
        assert(unit);
11,139✔
1301
        assert(ret);
11,139✔
1302

1303
        if (streq(unit, SPECIAL_ROOT_SLICE))
11,139✔
1304
                return strdup_to(ret, "");
7✔
1305

1306
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
11,132✔
1307
                return -EINVAL;
1308

1309
        if (!endswith(unit, ".slice"))
11,121✔
1310
                return -EINVAL;
1311

1312
        r = unit_name_to_prefix(unit, &p);
11,120✔
1313
        if (r < 0)
11,120✔
1314
                return r;
1315

1316
        dash = strchr(p, '-');
11,120✔
1317

1318
        /* Don't allow initial dashes */
1319
        if (dash == p)
11,120✔
1320
                return -EINVAL;
1321

1322
        while (dash) {
11,703✔
1323
                _cleanup_free_ char *escaped = NULL;
588✔
1324
                char n[dash - p + sizeof(".slice")];
588✔
1325

1326
#if HAS_FEATURE_MEMORY_SANITIZER
1327
                /* msan doesn't instrument stpncpy, so it thinks
1328
                 * n is later used uninitialized:
1329
                 * https://github.com/google/sanitizers/issues/926
1330
                 */
1331
                zero(n);
1332
#endif
1333

1334
                /* Don't allow trailing or double dashes */
1335
                if (IN_SET(dash[1], 0, '-'))
588✔
1336
                        return -EINVAL;
1337

1338
                strcpy(stpncpy(n, p, dash - p), ".slice");
586✔
1339
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
586✔
1340
                        return -EINVAL;
1341

1342
                r = cg_escape(n, &escaped);
586✔
1343
                if (r < 0)
586✔
1344
                        return r;
1345

1346
                if (!strextend(&s, escaped, "/"))
586✔
1347
                        return -ENOMEM;
1348

1349
                dash = strchr(dash+1, '-');
586✔
1350
        }
1351

1352
        r = cg_escape(unit, &e);
11,115✔
1353
        if (r < 0)
11,115✔
1354
                return r;
1355

1356
        if (!strextend(&s, e))
11,115✔
1357
                return -ENOMEM;
1358

1359
        *ret = TAKE_PTR(s);
11,115✔
1360
        return 0;
11,115✔
1361
}
1362

1363
int cg_is_threaded(const char *path) {
×
1364
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
1365
        _cleanup_strv_free_ char **v = NULL;
×
1366
        int r;
×
1367

1368
        r = cg_get_path(path, "cgroup.type", &fs);
×
1369
        if (r < 0)
×
1370
                return r;
1371

1372
        r = read_full_virtual_file(fs, &contents, NULL);
×
1373
        if (r == -ENOENT)
×
1374
                return false; /* Assume no. */
1375
        if (r < 0)
×
1376
                return r;
1377

1378
        v = strv_split(contents, NULL);
×
1379
        if (!v)
×
1380
                return -ENOMEM;
1381

1382
        /* If the cgroup is in the threaded mode, it contains "threaded".
1383
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
1384
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1385
}
1386

1387
int cg_set_attribute(const char *path, const char *attribute, const char *value) {
50,946✔
1388
        _cleanup_free_ char *p = NULL;
50,946✔
1389
        int r;
50,946✔
1390

1391
        assert(attribute);
50,946✔
1392

1393
        r = cg_get_path(path, attribute, &p);
50,946✔
1394
        if (r < 0)
50,946✔
1395
                return r;
1396

1397
        /* https://lore.kernel.org/all/20250419183545.1982187-1-shakeel.butt@linux.dev/ adds O_NONBLOCK
1398
         * semantics to memory.max and memory.high to skip synchronous memory reclaim when O_NONBLOCK is
1399
         * enabled. Let's always open cgroupv2 attribute files in nonblocking mode to immediately take
1400
         * advantage of this and any other asynchronous resource reclaim that's added to the cgroupv2 API in
1401
         * the future. */
1402
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER|WRITE_STRING_FILE_OPEN_NONBLOCKING);
50,946✔
1403
}
1404

1405
int cg_get_attribute(const char *path, const char *attribute, char **ret) {
53,309✔
1406
        _cleanup_free_ char *p = NULL;
53,309✔
1407
        int r;
53,309✔
1408

1409
        assert(attribute);
53,309✔
1410

1411
        r = cg_get_path(path, attribute, &p);
53,309✔
1412
        if (r < 0)
53,309✔
1413
                return r;
1414

1415
        return read_one_line_file(p, ret);
53,309✔
1416
}
1417

1418
int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t *ret) {
44,175✔
1419
        _cleanup_free_ char *value = NULL;
44,175✔
1420
        uint64_t v;
44,175✔
1421
        int r;
44,175✔
1422

1423
        assert(ret);
44,175✔
1424

1425
        r = cg_get_attribute(path, attribute, &value);
44,175✔
1426
        if (r == -ENOENT)
44,175✔
1427
                return -ENODATA;
1428
        if (r < 0)
41,721✔
1429
                return r;
1430

1431
        if (streq(value, "max")) {
41,721✔
1432
                *ret = CGROUP_LIMIT_MAX;
10,331✔
1433
                return 0;
10,331✔
1434
        }
1435

1436
        r = safe_atou64(value, &v);
31,390✔
1437
        if (r < 0)
31,390✔
1438
                return r;
1439

1440
        *ret = v;
31,390✔
1441
        return 0;
31,390✔
1442
}
1443

1444
int cg_get_attribute_as_bool(const char *path, const char *attribute) {
87✔
1445
        _cleanup_free_ char *value = NULL;
87✔
1446
        int r;
87✔
1447

1448
        r = cg_get_attribute(path, attribute, &value);
87✔
1449
        if (r == -ENOENT)
87✔
1450
                return -ENODATA;
1451
        if (r < 0)
87✔
1452
                return r;
1453

1454
        return parse_boolean(value);
87✔
1455
}
1456

1457
int cg_get_owner(const char *path, uid_t *ret_uid) {
38✔
1458
        _cleanup_free_ char *f = NULL;
38✔
1459
        struct stat stats;
38✔
1460
        int r;
38✔
1461

1462
        assert(ret_uid);
38✔
1463

1464
        r = cg_get_path(path, /* suffix= */ NULL, &f);
38✔
1465
        if (r < 0)
38✔
1466
                return r;
1467

1468
        if (stat(f, &stats) < 0)
38✔
1469
                return -errno;
16✔
1470

1471
        r = stat_verify_directory(&stats);
22✔
1472
        if (r < 0)
22✔
1473
                return r;
1474

1475
        *ret_uid = stats.st_uid;
22✔
1476
        return 0;
22✔
1477
}
1478

1479
int cg_get_keyed_attribute(
26,859✔
1480
                const char *path,
1481
                const char *attribute,
1482
                char * const *keys,
1483
                char **values) {
1484

1485
        _cleanup_free_ char *filename = NULL, *contents = NULL;
26,859✔
1486
        size_t n;
26,859✔
1487
        int r;
26,859✔
1488

1489
        assert(path);
26,859✔
1490
        assert(attribute);
26,859✔
1491

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

1498
        r = cg_get_path(path, attribute, &filename);
26,859✔
1499
        if (r < 0)
26,859✔
1500
                return r;
1501

1502
        r = read_full_file(filename, &contents, /* ret_size= */ NULL);
26,859✔
1503
        if (r < 0)
26,859✔
1504
                return r;
1505

1506
        n = strv_length(keys);
24,245✔
1507
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
24,245✔
1508
                return 0;
1509
        assert(strv_is_uniq(keys));
24,245✔
1510

1511
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1512
        char **v = newa0(char*, n);
24,245✔
1513
        size_t n_done = 0;
24,245✔
1514

1515
        for (const char *p = contents; *p;) {
79,608✔
1516
                const char *w;
1517
                size_t i;
1518

1519
                for (i = 0; i < n; i++) {
134,979✔
1520
                        w = first_word(p, keys[i]);
87,734✔
1521
                        if (w)
87,734✔
1522
                                break;
1523
                }
1524

1525
                if (w) {
79,606✔
1526
                        if (v[i]) { /* duplicate entry? */
32,361✔
1527
                                r = -EBADMSG;
×
1528
                                goto fail;
×
1529
                        }
1530

1531
                        size_t l = strcspn(w, NEWLINE);
32,361✔
1532

1533
                        v[i] = strndup(w, l);
32,361✔
1534
                        if (!v[i]) {
32,361✔
1535
                                r = -ENOMEM;
×
1536
                                goto fail;
×
1537
                        }
1538

1539
                        n_done++;
32,361✔
1540
                        if (n_done >= n)
32,361✔
1541
                                break;
1542

1543
                        p = w + l;
8,118✔
1544
                } else
1545
                        p += strcspn(p, NEWLINE);
47,245✔
1546

1547
                p += strspn(p, NEWLINE);
55,363✔
1548
        }
1549

1550
        if (n_done < n) {
24,245✔
1551
                r = -ENXIO;
2✔
1552
                goto fail;
2✔
1553
        }
1554

1555
        memcpy(values, v, sizeof(char*) * n);
24,243✔
1556
        return 0;
24,243✔
1557

1558
fail:
2✔
1559
        free_many_charp(v, n);
26,861✔
1560
        return r;
1561
}
1562

1563
int cg_mask_to_string(CGroupMask mask, char **ret) {
10,400✔
1564
        _cleanup_free_ char *s = NULL;
10,400✔
1565
        bool space = false;
10,400✔
1566
        CGroupController c;
10,400✔
1567
        size_t n = 0;
10,400✔
1568

1569
        assert(ret);
10,400✔
1570

1571
        if (mask == 0) {
10,400✔
1572
                *ret = NULL;
5,793✔
1573
                return 0;
5,793✔
1574
        }
1575

1576
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
69,105✔
1577
                const char *k;
64,498✔
1578
                size_t l;
64,498✔
1579

1580
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
64,498✔
1581
                        continue;
38,938✔
1582

1583
                k = cgroup_controller_to_string(c);
25,560✔
1584
                l = strlen(k);
25,560✔
1585

1586
                if (!GREEDY_REALLOC(s, n + space + l + 1))
25,560✔
1587
                        return -ENOMEM;
1588

1589
                if (space)
25,560✔
1590
                        s[n] = ' ';
20,953✔
1591
                memcpy(s + n + space, k, l);
25,560✔
1592
                n += space + l;
25,560✔
1593

1594
                space = true;
25,560✔
1595
        }
1596

1597
        assert(s);
4,607✔
1598

1599
        s[n] = 0;
4,607✔
1600
        *ret = TAKE_PTR(s);
4,607✔
1601

1602
        return 0;
4,607✔
1603
}
1604

1605
int cg_mask_from_string(const char *s, CGroupMask *ret) {
4,380✔
1606
        CGroupMask m = 0;
4,380✔
1607

1608
        assert(ret);
4,380✔
1609
        assert(s);
4,380✔
1610

1611
        for (;;) {
29,493✔
1612
                _cleanup_free_ char *n = NULL;
25,113✔
1613
                CGroupController v;
29,493✔
1614
                int r;
29,493✔
1615

1616
                r = extract_first_word(&s, &n, NULL, 0);
29,493✔
1617
                if (r < 0)
29,493✔
1618
                        return r;
×
1619
                if (r == 0)
29,493✔
1620
                        break;
1621

1622
                v = cgroup_controller_from_string(n);
25,113✔
1623
                if (v < 0)
25,113✔
1624
                        continue;
762✔
1625

1626
                m |= CGROUP_CONTROLLER_TO_MASK(v);
24,351✔
1627
        }
1628

1629
        *ret = m;
4,380✔
1630
        return 0;
4,380✔
1631
}
1632

1633
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
503✔
1634
        CGroupMask mask;
503✔
1635
        int r;
503✔
1636

1637
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1638
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1639
         * pseudo-controllers. */
1640

1641
        /* We can read the supported and accessible controllers from the top-level cgroup attribute */
1642
        _cleanup_free_ char *controllers = NULL, *path = NULL;
503✔
1643
        r = cg_get_path(root, "cgroup.controllers", &path);
503✔
1644
        if (r < 0)
503✔
1645
                return r;
1646

1647
        r = read_one_line_file(path, &controllers);
503✔
1648
        if (r < 0)
503✔
1649
                return r;
1650

1651
        r = cg_mask_from_string(controllers, &mask);
503✔
1652
        if (r < 0)
503✔
1653
                return r;
1654

1655
        /* Mask controllers that are not supported in cgroup v2. */
1656
        mask &= CGROUP_MASK_V2;
503✔
1657

1658
        *ret = mask;
503✔
1659
        return 0;
503✔
1660
}
1661

1662
int cg_mask_supported(CGroupMask *ret) {
249✔
1663
        _cleanup_free_ char *root = NULL;
249✔
1664
        int r;
249✔
1665

1666
        r = cg_get_root_path(&root);
249✔
1667
        if (r < 0)
249✔
1668
                return r;
1669

1670
        return cg_mask_supported_subtree(root, ret);
249✔
1671
}
1672

1673
int cg_is_delegated(const char *path) {
18✔
1674
        int r;
18✔
1675

1676
        assert(path);
18✔
1677

1678
        r = cg_get_xattr_bool(path, "trusted.delegate");
18✔
1679
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
18✔
1680
                return r;
1681

1682
        /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the assumption
1683
         * that whoever is trusted enough to own the cgroup, is also trusted enough to decide if it is
1684
         * delegated or not this should be safe. */
1685
        r = cg_get_xattr_bool(path, "user.delegate");
6✔
1686
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
6✔
1687
}
1688

1689
int cg_is_delegated_fd(int fd) {
192✔
1690
        int r;
192✔
1691

1692
        assert(fd >= 0);
192✔
1693

1694
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
192✔
1695
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
192✔
1696
                return r;
1697

1698
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
178✔
1699
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
178✔
1700
}
1701

1702
int cg_has_coredump_receive(const char *path) {
2✔
1703
        int r;
2✔
1704

1705
        assert(path);
2✔
1706

1707
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
1708
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
1709
                return false;
×
1710

1711
        return r;
1712
}
1713

1714
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1715
        [CGROUP_IO_RBPS_MAX]  = CGROUP_LIMIT_MAX,
1716
        [CGROUP_IO_WBPS_MAX]  = CGROUP_LIMIT_MAX,
1717
        [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX,
1718
        [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX,
1719
};
1720

1721
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1722
        [CGROUP_IO_RBPS_MAX]  = "IOReadBandwidthMax",
1723
        [CGROUP_IO_WBPS_MAX]  = "IOWriteBandwidthMax",
1724
        [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax",
1725
        [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax",
1726
};
1727

1728
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
12,740✔
1729

1730
void cgroup_io_limits_list(void) {
20✔
1731
        DUMP_STRING_TABLE(cgroup_io_limit_type, CGroupIOLimitType, _CGROUP_IO_LIMIT_TYPE_MAX);
100✔
1732
}
20✔
1733

1734
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
1735
        [CGROUP_CONTROLLER_CPU]                             = "cpu",
1736
        [CGROUP_CONTROLLER_CPUACCT]                         = "cpuacct",
1737
        [CGROUP_CONTROLLER_CPUSET]                          = "cpuset",
1738
        [CGROUP_CONTROLLER_IO]                              = "io",
1739
        [CGROUP_CONTROLLER_BLKIO]                           = "blkio",
1740
        [CGROUP_CONTROLLER_MEMORY]                          = "memory",
1741
        [CGROUP_CONTROLLER_DEVICES]                         = "devices",
1742
        [CGROUP_CONTROLLER_PIDS]                            = "pids",
1743
        [CGROUP_CONTROLLER_BPF_FIREWALL]                    = "bpf-firewall",
1744
        [CGROUP_CONTROLLER_BPF_DEVICES]                     = "bpf-devices",
1745
        [CGROUP_CONTROLLER_BPF_FOREIGN]                     = "bpf-foreign",
1746
        [CGROUP_CONTROLLER_BPF_SOCKET_BIND]                 = "bpf-socket-bind",
1747
        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
1748
        [CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE]      = "bpf-bind-network-interface",
1749
};
1750

1751
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
442,775✔
1752

1753
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
1754
        [MANAGED_OOM_AUTO] = "auto",
1755
        [MANAGED_OOM_KILL] = "kill",
1756
};
1757

1758
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
32,525✔
1759

1760
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
1761
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
1762
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
1763
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
1764
};
1765

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