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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

92.82
/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) {
26✔
39
        struct statfs fs;
26✔
40

41
        if (statfs("/sys/fs/cgroup/", &fs) < 0) {
26✔
42
                if (errno == ENOENT) /* sysfs not mounted? */
×
43
                        return false;
26✔
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);
26✔
49
}
50

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

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

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

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

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

70
                cgroupfs_fd = fsfd;
71
        }
72

73
        union {
15✔
74
                struct file_handle file_handle;
75
                uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
76
        } fh = {
15✔
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);
15✔
82

83
        return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
22✔
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) {
24,975✔
108
        _cleanup_free_ char *fs = NULL;
24,975✔
109
        FILE *f;
24,975✔
110
        int r;
24,975✔
111

112
        assert(ret);
24,975✔
113

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

118
        f = fopen(fs, "re");
24,975✔
119
        if (!f)
24,975✔
120
                return -errno;
13,932✔
121

122
        *ret = f;
11,043✔
123
        return 0;
11,043✔
124
}
125

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

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

131
        assert(f);
16,947✔
132
        assert(ret);
16,947✔
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 (;;) {
16,947✔
138
                errno = 0;
16,947✔
139
                if (fscanf(f, "%lu", &ul) != 1) {
16,947✔
140

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

UNCOV
146
                        return errno_or_else(EIO);
×
147
                }
148

149
                if (ul > PID_T_MAX)
5,738✔
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))
5,738✔
156
                        continue;
×
157

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

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

166
        assert(f);
13,741✔
167
        assert(ret);
13,741✔
168

169
        for (;;) {
3✔
170
                pid_t pid;
13,744✔
171

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

180
                if (pid == 0)
3,117✔
181
                        return -EREMOTE;
182

183
                r = pidref_set_pid(ret, pid);
3,117✔
184
                if (r >= 0)
3,117✔
185
                        return 1;
186
                if (r != -ESRCH)
3✔
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) {
3✔
194
        static thread_local int supported = -1;
3✔
195

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

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

202
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
3✔
203
                return (supported = true);
3✔
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) {
24,567✔
210
        _cleanup_free_ char *fs = NULL;
24,567✔
211
        DIR *d;
24,567✔
212
        int r;
24,567✔
213

214
        assert(ret);
24,567✔
215

216
        /* This is not recursive! */
217

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

222
        d = opendir(fs);
24,567✔
223
        if (!d)
24,567✔
224
                return -errno;
13,932✔
225

226
        *ret = d;
10,635✔
227
        return 0;
10,635✔
228
}
229

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

234
        FOREACH_DIRENT_ALL(de, d, return -errno) {
498,216✔
235
                if (de->d_type != DT_DIR)
487,415✔
236
                        continue;
464,594✔
237

238
                if (dot_or_dot_dot(de->d_name))
22,821✔
239
                        continue;
21,602✔
240

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

244
        *ret = NULL;
10,801✔
245
        return 0;
10,801✔
246
}
247

248
int cg_kill(
24,398✔
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;
24,398✔
257
        int r, ret = 0;
24,398✔
258

259
        assert(path);
24,398✔
260
        assert(sig >= 0);
24,398✔
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))
24,398✔
265
                flags &= ~CGROUP_SIGCONT;
3,800✔
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) {
24,398✔
273
                killed_pids = allocated_set = set_new(NULL);
636✔
274
                if (!killed_pids)
636✔
275
                        return -ENOMEM;
276
        }
277

278
        bool done;
24,511✔
279
        do {
24,511✔
280
                _cleanup_fclose_ FILE *f = NULL;
13,932✔
281
                int ret_log_kill;
24,511✔
282

283
                done = true;
24,511✔
284

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

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

294
                        r = cg_read_pidref(f, &pidref, flags);
13,570✔
295
                        if (r == -ENODEV) {
13,570✔
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)
13,570✔
303
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
304
                        if (r == 0)
13,570✔
305
                                break;
306

307
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
2,991✔
308
                                continue;
637✔
309

310
                        if (set_contains(killed_pids, PID_TO_PTR(pidref.pid)))
2,354✔
311
                                continue;
1,861✔
312

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

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

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

330
                                if (ret == 0) {
493✔
331
                                        if (log_kill)
274✔
332
                                                ret = ret_log_kill;
333
                                        else
334
                                                ret = 1;
78✔
335
                                }
336
                        }
337

338
                        done = false;
493✔
339

340
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
493✔
341
                        if (r < 0)
493✔
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);
10,579✔
349

350
        return ret;
351
}
352

353
int cg_kill_recursive(
23,758✔
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;
23,758✔
363
        int r, ret;
23,758✔
364

365
        assert(path);
23,758✔
366
        assert(sig >= 0);
23,758✔
367

368
        if (!killed_pids) {
23,758✔
369
                killed_pids = allocated_set = set_new(NULL);
22,379✔
370
                if (!killed_pids)
22,379✔
371
                        return -ENOMEM;
372
        }
373

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

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

381
                return ret;
382
        }
383

384
        for (;;) {
10,220✔
385
                _cleanup_free_ char *fn = NULL, *p = NULL;
10,023✔
386

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

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

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

406
        return ret;
9,826✔
407
}
408

409
int cg_kill_kernel_sigkill(const char *path) {
3✔
410
        _cleanup_free_ char *killfile = NULL;
3✔
411
        int r;
3✔
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);
3✔
417

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

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

425
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
3✔
426
        if (r < 0)
3✔
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) {
543,769✔
433
        char *t;
543,769✔
434

435
        assert(ret);
543,769✔
436

437
        if (isempty(path))
597,425✔
438
                path = TAKE_PTR(suffix);
439

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

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

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

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

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

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

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

467
        assert(path);
29,264✔
468
        assert(name);
29,264✔
469

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

474
        return lgetxattr_malloc(fs, name, ret, ret_size);
29,264✔
475
}
476

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

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

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

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

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

495
        assert(path);
74,663✔
496
        assert(name);
74,663✔
497

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

502
        return RET_NERRNO(removexattr(fs, name));
149,326✔
503
}
504

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

510
        assert(pid >= 0);
104,983✔
511
        assert(ret_path);
104,983✔
512

513
        fs = procfs_file_alloca(pid, "cgroup");
118,283✔
514
        r = fopen_unlocked(fs, "re", &f);
104,983✔
515
        if (r == -ENOENT)
104,983✔
516
                return -ESRCH;
517
        if (r < 0)
100,582✔
518
                return r;
519

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

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

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

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

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

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

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

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

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

560
        assert(ret_path);
33,802✔
561

562
        if (!pidref_is_set(pidref))
33,802✔
563
                return -ESRCH;
564
        if (pidref_is_remote(pidref))
67,604✔
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);
33,802✔
572
        if (r < 0)
33,802✔
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);
32,714✔
577
        if (r < 0)
32,714✔
578
                return r;
579

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

584
int cg_is_empty(const char *path) {
4,672✔
585
        _cleanup_free_ char *t = NULL;
4,672✔
586
        int r;
4,672✔
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);
4,672✔
592

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

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

603
        return streq(t, "0");
811✔
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) {
48,594✔
669
        char *p, *e;
48,594✔
670
        int r;
48,594✔
671

672
        assert(ret_path);
48,594✔
673

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

678
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
48,594✔
679
        if (e)
48,594✔
680
                *e = 0;
48,556✔
681

682
        *ret_path = p;
48,594✔
683
        return 0;
48,594✔
684
}
685

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

689
        assert(cgroup);
16,145✔
690
        assert(ret_shifted);
16,145✔
691

692
        _cleanup_free_ char *rt = NULL;
16,145✔
693
        if (!root) {
16,145✔
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,805✔
698
                if (r < 0)
3,805✔
699
                        return r;
700

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

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

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

713
        assert(pid >= 0);
19,277✔
714
        assert(ret_cgroup);
19,277✔
715

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

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

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

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

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

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

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

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

745
        if (ret_unit)
40,692✔
746
                return strdup_to(ret_unit, c);
40,692✔
747

748
        return 0;
749
}
750

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

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

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

761
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
74,503✔
762
}
763

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

767
        /* Skips over all slice assignments */
768

769
        for (;;) {
160,478✔
770
                size_t n;
106,314✔
771

772
                p += strspn(p, "/");
106,314✔
773

774
                n = strcspn(p, "/");
106,314✔
775
                if (!valid_slice_name(p, n))
106,314✔
776
                        return p;
52,150✔
777

778
                p += n;
54,164✔
779
        }
780
}
781

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

785
        assert(path);
21,571✔
786

787
        const char *e = skip_slices(path);
21,571✔
788

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

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

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

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

811
                path_simplify(subgroup);
839✔
812

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

816
        if (ret_unit)
21,550✔
817
                *ret_unit = TAKE_PTR(unit);
21,550✔
818

819
        return 0;
820
}
821

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

826
        assert(path);
12,174✔
827
        assert(ret);
12,174✔
828

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

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

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

839
        *ret = TAKE_PTR(path_copy);
12,171✔
840

841
        return 0;
12,171✔
842
}
843

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

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

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

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

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

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

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

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

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

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

884
        if (isempty(p))
17,990✔
885
                return NULL;
886

887
        p += strspn(p, "/");
17,986✔
888

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

893
        const char *s = startswith(p, "session-");
17,784✔
894
        if (!s)
17,784✔
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),
35✔
900
             *e = endswith(f, ".scope");
35✔
901
        if (!e)
35✔
902
                return NULL;
903
        *e = '\0';
35✔
904

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

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

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

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

916
        if (isempty(p))
18,405✔
917
                return NULL;
18,405✔
918

919
        p += strspn(p, "/");
18,401✔
920

921
        n = strcspn(p, "/");
18,401✔
922
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
18,401✔
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);
18,201✔
928
        if (!unit_name)
18,201✔
929
                return NULL;
930

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

934
        if (type != UNIT_NAME_INSTANCE)
18,201✔
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) &&
708✔
941
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
154✔
942
                return NULL;
943

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

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

950
        assert(path);
18,405✔
951

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

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

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

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

967
        assert(path);
9,231✔
968

969
        t = skip_user_prefix(path);
9,231✔
970
        if (!t)
9,231✔
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);
233✔
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) {
13✔
990
        int r;
13✔
991

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

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

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

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

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

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

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

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

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

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

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

1042
        assert(path);
11,119✔
1043

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

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

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

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

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

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

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

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

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

1079
        if (!pidref_is_set(pidref))
517✔
1080
                return -ESRCH;
517✔
1081
        if (pidref_is_remote(pidref))
1,034✔
1082
                return -EREMOTE;
1083

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

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

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

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

1103
        assert(path);
9,997✔
1104

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

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

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

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

1121
        return 0;
1122
}
1123

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

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

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

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

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

1143
        uid_t uid;
55✔
1144
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
55✔
1145
        if (r < 0)
55✔
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) {
19,443✔
1159
        const char *e = NULL;
19,443✔
1160

1161
        assert(p);
19,443✔
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 (;;) {
60,121✔
1167
                const char *s;
39,782✔
1168
                int n;
39,782✔
1169

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

1176
                e = s;
20,339✔
1177
        }
1178

1179
        if (e)
19,443✔
1180
                return cg_path_decode_unit(e, ret_slice);
19,137✔
1181

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

1185
        return 0;
1186
}
1187

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

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

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

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

1203
        t = skip_user_prefix(p);
9,174✔
1204
        if (!t)
9,174✔
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);
217✔
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) {
35,892✔
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))
35,892✔
1231
                return true;
1232

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

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

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

1242
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
538,170✔
1243
                const char *q;
502,292✔
1244

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

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

1253
        return false;
1254
}
1255

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

1259
        assert(ret);
35,278✔
1260

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

1266
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1267

1268
        if (cg_needs_escape(p)) {
35,278✔
1269
                n = strjoin("_", p);
7✔
1270
                if (!n)
7✔
1271
                        return -ENOMEM;
1272

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

1281
        *ret = TAKE_PTR(n);
35,278✔
1282
        return 0;
35,278✔
1283
}
1284

1285
char* cg_unescape(const char *p) {
127,537✔
1286
        assert(p);
127,537✔
1287

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

1291
        if (p[0] == '_')
127,537✔
1292
                return (char*) p+1;
14✔
1293

1294
        return (char*) p;
1295
}
1296

1297
int cg_slice_to_path(const char *unit, char **ret) {
15,677✔
1298
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
15,677✔
1299
        const char *dash;
15,677✔
1300
        int r;
15,677✔
1301

1302
        assert(unit);
15,677✔
1303
        assert(ret);
15,677✔
1304

1305
        if (streq(unit, SPECIAL_ROOT_SLICE))
15,677✔
1306
                return strdup_to(ret, "");
7✔
1307

1308
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
15,670✔
1309
                return -EINVAL;
1310

1311
        if (!endswith(unit, ".slice"))
15,659✔
1312
                return -EINVAL;
1313

1314
        r = unit_name_to_prefix(unit, &p);
15,658✔
1315
        if (r < 0)
15,658✔
1316
                return r;
1317

1318
        dash = strchr(p, '-');
15,658✔
1319

1320
        /* Don't allow initial dashes */
1321
        if (dash == p)
15,658✔
1322
                return -EINVAL;
1323

1324
        while (dash) {
16,514✔
1325
                _cleanup_free_ char *escaped = NULL;
861✔
1326
                char n[dash - p + sizeof(".slice")];
861✔
1327

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

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

1340
                strcpy(stpncpy(n, p, dash - p), ".slice");
859✔
1341
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
859✔
1342
                        return -EINVAL;
1343

1344
                r = cg_escape(n, &escaped);
859✔
1345
                if (r < 0)
859✔
1346
                        return r;
1347

1348
                if (!strextend(&s, escaped, "/"))
859✔
1349
                        return -ENOMEM;
1350

1351
                dash = strchr(dash+1, '-');
859✔
1352
        }
1353

1354
        r = cg_escape(unit, &e);
15,653✔
1355
        if (r < 0)
15,653✔
1356
                return r;
1357

1358
        if (!strextend(&s, e))
15,653✔
1359
                return -ENOMEM;
1360

1361
        *ret = TAKE_PTR(s);
15,653✔
1362
        return 0;
15,653✔
1363
}
1364

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

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

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

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

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

1389
int cg_set_attribute(const char *path, const char *attribute, const char *value) {
103,811✔
1390
        _cleanup_free_ char *p = NULL;
103,811✔
1391
        int r;
103,811✔
1392

1393
        assert(attribute);
103,811✔
1394

1395
        r = cg_get_path(path, attribute, &p);
103,811✔
1396
        if (r < 0)
103,811✔
1397
                return r;
1398

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

1407
int cg_get_attribute(const char *path, const char *attribute, char **ret) {
110,320✔
1408
        _cleanup_free_ char *p = NULL;
110,320✔
1409
        int r;
110,320✔
1410

1411
        assert(attribute);
110,320✔
1412

1413
        r = cg_get_path(path, attribute, &p);
110,320✔
1414
        if (r < 0)
110,320✔
1415
                return r;
1416

1417
        return read_one_line_file(p, ret);
110,320✔
1418
}
1419

1420
int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t *ret) {
91,787✔
1421
        _cleanup_free_ char *value = NULL;
91,787✔
1422
        uint64_t v;
91,787✔
1423
        int r;
91,787✔
1424

1425
        assert(ret);
91,787✔
1426

1427
        r = cg_get_attribute(path, attribute, &value);
91,787✔
1428
        if (r == -ENOENT)
91,787✔
1429
                return -ENODATA;
1430
        if (r < 0)
88,643✔
1431
                return r;
1432

1433
        if (streq(value, "max")) {
88,643✔
1434
                *ret = CGROUP_LIMIT_MAX;
23,059✔
1435
                return 0;
23,059✔
1436
        }
1437

1438
        r = safe_atou64(value, &v);
65,584✔
1439
        if (r < 0)
65,584✔
1440
                return r;
1441

1442
        *ret = v;
65,584✔
1443
        return 0;
65,584✔
1444
}
1445

1446
int cg_get_attribute_as_bool(const char *path, const char *attribute) {
73✔
1447
        _cleanup_free_ char *value = NULL;
73✔
1448
        int r;
73✔
1449

1450
        r = cg_get_attribute(path, attribute, &value);
73✔
1451
        if (r == -ENOENT)
73✔
1452
                return -ENODATA;
1453
        if (r < 0)
73✔
1454
                return r;
1455

1456
        return parse_boolean(value);
73✔
1457
}
1458

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

1464
        assert(ret_uid);
38✔
1465

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

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

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

1477
        *ret_uid = stats.st_uid;
22✔
1478
        return 0;
22✔
1479
}
1480

1481
int cg_get_keyed_attribute(
54,340✔
1482
                const char *path,
1483
                const char *attribute,
1484
                char * const *keys,
1485
                char **values) {
1486

1487
        _cleanup_free_ char *filename = NULL, *contents = NULL;
54,340✔
1488
        size_t n;
54,340✔
1489
        int r;
54,340✔
1490

1491
        assert(path);
54,340✔
1492
        assert(attribute);
54,340✔
1493

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

1500
        r = cg_get_path(path, attribute, &filename);
54,340✔
1501
        if (r < 0)
54,340✔
1502
                return r;
1503

1504
        r = read_full_file(filename, &contents, /* ret_size= */ NULL);
54,340✔
1505
        if (r < 0)
54,340✔
1506
                return r;
1507

1508
        n = strv_length(keys);
50,413✔
1509
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
50,413✔
1510
                return 0;
1511
        assert(strv_is_uniq(keys));
50,413✔
1512

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

1517
        for (const char *p = contents; *p;) {
167,399✔
1518
                const char *w;
1519
                size_t i;
1520

1521
                for (i = 0; i < n; i++) {
284,393✔
1522
                        w = first_word(p, keys[i]);
182,657✔
1523
                        if (w)
182,657✔
1524
                                break;
1525
                }
1526

1527
                if (w) {
167,397✔
1528
                        if (v[i]) { /* duplicate entry? */
65,661✔
1529
                                r = -EBADMSG;
×
1530
                                goto fail;
×
1531
                        }
1532

1533
                        size_t l = strcspn(w, NEWLINE);
65,661✔
1534

1535
                        v[i] = strndup(w, l);
65,661✔
1536
                        if (!v[i]) {
65,661✔
1537
                                r = -ENOMEM;
×
1538
                                goto fail;
×
1539
                        }
1540

1541
                        n_done++;
65,661✔
1542
                        if (n_done >= n)
65,661✔
1543
                                break;
1544

1545
                        p = w + l;
15,250✔
1546
                } else
1547
                        p += strcspn(p, NEWLINE);
101,736✔
1548

1549
                p += strspn(p, NEWLINE);
116,986✔
1550
        }
1551

1552
        if (n_done < n) {
50,413✔
1553
                r = -ENXIO;
2✔
1554
                goto fail;
2✔
1555
        }
1556

1557
        memcpy(values, v, sizeof(char*) * n);
50,411✔
1558
        return 0;
50,411✔
1559

1560
fail:
2✔
1561
        free_many_charp(v, n);
54,342✔
1562
        return r;
1563
}
1564

1565
int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret) {
34,394✔
1566
        _cleanup_free_ char *val = NULL;
34,394✔
1567
        int r;
34,394✔
1568

1569
        assert(key);
34,394✔
1570
        assert(ret);
34,394✔
1571

1572
        r = cg_get_keyed_attribute(path, attribute, STRV_MAKE(key), &val);
34,394✔
1573
        if (r < 0)
34,394✔
1574
                return r;
1575

1576
        r = safe_atou64(val, ret);
34,352✔
1577
        if (r < 0)
34,352✔
1578
                return log_debug_errno(r, "Failed to parse value '%s' of key '%s' in cgroup attribute '%s': %m", val, key, attribute);
×
1579

1580
        return 0;
1581
}
1582

1583
int cg_mask_to_string(CGroupMask mask, char **ret) {
23,118✔
1584
        _cleanup_free_ char *s = NULL;
23,118✔
1585
        bool space = false;
23,118✔
1586
        CGroupController c;
23,118✔
1587
        size_t n = 0;
23,118✔
1588

1589
        assert(ret);
23,118✔
1590

1591
        if (mask == 0) {
23,118✔
1592
                *ret = NULL;
9,791✔
1593
                return 0;
9,791✔
1594
        }
1595

1596
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
199,905✔
1597
                const char *k;
186,578✔
1598
                size_t l;
186,578✔
1599

1600
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
186,578✔
1601
                        continue;
100,943✔
1602

1603
                k = cgroup_controller_to_string(c);
85,635✔
1604
                l = strlen(k);
85,635✔
1605

1606
                if (!GREEDY_REALLOC(s, n + space + l + 1))
85,635✔
1607
                        return -ENOMEM;
1608

1609
                if (space)
85,635✔
1610
                        s[n] = ' ';
72,308✔
1611
                memcpy(s + n + space, k, l);
85,635✔
1612
                n += space + l;
85,635✔
1613

1614
                space = true;
85,635✔
1615
        }
1616

1617
        assert(s);
13,327✔
1618

1619
        s[n] = 0;
13,327✔
1620
        *ret = TAKE_PTR(s);
13,327✔
1621

1622
        return 0;
13,327✔
1623
}
1624

1625
int cg_mask_from_string(const char *s, CGroupMask *ret) {
12,942✔
1626
        CGroupMask m = 0;
12,942✔
1627

1628
        assert(ret);
12,942✔
1629
        assert(s);
12,942✔
1630

1631
        for (;;) {
97,338✔
1632
                _cleanup_free_ char *n = NULL;
84,396✔
1633
                CGroupController v;
97,338✔
1634
                int r;
97,338✔
1635

1636
                r = extract_first_word(&s, &n, NULL, 0);
97,338✔
1637
                if (r < 0)
97,338✔
1638
                        return r;
×
1639
                if (r == 0)
97,338✔
1640
                        break;
1641

1642
                v = cgroup_controller_from_string(n);
84,396✔
1643
                if (v < 0)
84,396✔
1644
                        continue;
890✔
1645

1646
                m |= CGROUP_CONTROLLER_TO_MASK(v);
83,506✔
1647
        }
1648

1649
        *ret = m;
12,942✔
1650
        return 0;
12,942✔
1651
}
1652

1653
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
564✔
1654
        CGroupMask mask;
564✔
1655
        int r;
564✔
1656

1657
        assert(ret);
564✔
1658

1659
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1660
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1661
         * pseudo-controllers. */
1662

1663
        /* We can read the supported and accessible controllers from the top-level cgroup attribute */
1664
        _cleanup_free_ char *controllers = NULL, *path = NULL;
564✔
1665
        r = cg_get_path(root, "cgroup.controllers", &path);
564✔
1666
        if (r < 0)
564✔
1667
                return r;
1668

1669
        r = read_one_line_file(path, &controllers);
564✔
1670
        if (r < 0)
564✔
1671
                return r;
1672

1673
        r = cg_mask_from_string(controllers, &mask);
564✔
1674
        if (r < 0)
564✔
1675
                return r;
1676

1677
        /* Mask controllers that are not supported in cgroup v2. */
1678
        mask &= CGROUP_MASK_V2;
564✔
1679

1680
        *ret = mask;
564✔
1681
        return 0;
564✔
1682
}
1683

1684
int cg_mask_supported(CGroupMask *ret) {
273✔
1685
        _cleanup_free_ char *root = NULL;
273✔
1686
        int r;
273✔
1687

1688
        r = cg_get_root_path(&root);
273✔
1689
        if (r < 0)
273✔
1690
                return r;
1691

1692
        return cg_mask_supported_subtree(root, ret);
273✔
1693
}
1694

1695
int cg_is_delegated(const char *path) {
18✔
1696
        int r;
18✔
1697

1698
        assert(path);
18✔
1699

1700
        r = cg_get_xattr_bool(path, "trusted.delegate");
18✔
1701
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
18✔
1702
                return r;
1703

1704
        /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the assumption
1705
         * that whoever is trusted enough to own the cgroup, is also trusted enough to decide if it is
1706
         * delegated or not this should be safe. */
1707
        r = cg_get_xattr_bool(path, "user.delegate");
6✔
1708
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
6✔
1709
}
1710

1711
int cg_is_delegated_fd(int fd) {
150✔
1712
        int r;
150✔
1713

1714
        assert(fd >= 0);
150✔
1715

1716
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
150✔
1717
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
150✔
1718
                return r;
1719

1720
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
143✔
1721
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
143✔
1722
}
1723

1724
int cg_has_coredump_receive(const char *path) {
2✔
1725
        int r;
2✔
1726

1727
        assert(path);
2✔
1728

1729
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
1730
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
1731
                return false;
×
1732

1733
        return r;
1734
}
1735

1736
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1737
        [CGROUP_IO_RBPS_MAX]  = CGROUP_LIMIT_MAX,
1738
        [CGROUP_IO_WBPS_MAX]  = CGROUP_LIMIT_MAX,
1739
        [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX,
1740
        [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX,
1741
};
1742

1743
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1744
        [CGROUP_IO_RBPS_MAX]  = "IOReadBandwidthMax",
1745
        [CGROUP_IO_WBPS_MAX]  = "IOWriteBandwidthMax",
1746
        [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax",
1747
        [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax",
1748
};
1749

1750
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
31,078✔
1751

1752
void cgroup_io_limits_list(void) {
20✔
1753
        DUMP_STRING_TABLE(cgroup_io_limit_type, CGroupIOLimitType, _CGROUP_IO_LIMIT_TYPE_MAX);
100✔
1754
}
20✔
1755

1756
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
1757
        [CGROUP_CONTROLLER_CPU]                             = "cpu",
1758
        [CGROUP_CONTROLLER_CPUACCT]                         = "cpuacct",
1759
        [CGROUP_CONTROLLER_CPUSET]                          = "cpuset",
1760
        [CGROUP_CONTROLLER_IO]                              = "io",
1761
        [CGROUP_CONTROLLER_BLKIO]                           = "blkio",
1762
        [CGROUP_CONTROLLER_MEMORY]                          = "memory",
1763
        [CGROUP_CONTROLLER_DEVICES]                         = "devices",
1764
        [CGROUP_CONTROLLER_PIDS]                            = "pids",
1765
        [CGROUP_CONTROLLER_BPF_FIREWALL]                    = "bpf-firewall",
1766
        [CGROUP_CONTROLLER_BPF_DEVICES]                     = "bpf-devices",
1767
        [CGROUP_CONTROLLER_BPF_FOREIGN]                     = "bpf-foreign",
1768
        [CGROUP_CONTROLLER_BPF_SOCKET_BIND]                 = "bpf-socket-bind",
1769
        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
1770
        [CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE]      = "bpf-bind-network-interface",
1771
};
1772

1773
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
733,706✔
1774

1775
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
1776
        [MANAGED_OOM_AUTO] = "auto",
1777
        [MANAGED_OOM_KILL] = "kill",
1778
};
1779

1780
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
51,460✔
1781

1782
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
1783
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
1784
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
1785
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
1786
};
1787

1788
DEFINE_STRING_TABLE_LOOKUP(managed_oom_preference, ManagedOOMPreference);
25,284✔
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