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

systemd / systemd / 23172097450

16 Mar 2026 07:45PM UTC coverage: 72.568% (+0.06%) from 72.513%
23172097450

push

github

keszybz
add-ug-bo-translation

316055 of 435531 relevant lines covered (72.57%)

1182407.5 hits per line

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

91.1
/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) {
911✔
52
        _cleanup_free_ char *fs = NULL;
911✔
53
        int r;
911✔
54

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

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

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

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

70
                cgroupfs_fd = fsfd;
71
        }
72

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

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

112
        assert(ret);
18,225✔
113

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

118
        f = fopen(fs, "re");
18,225✔
119
        if (!f)
18,225✔
120
                return -errno;
11,482✔
121

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

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

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

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

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

146
                        return errno_or_else(EIO);
×
147
                }
148

149
                if (ul > PID_T_MAX)
4,506✔
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,506✔
156
                        continue;
53✔
157

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

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

166
        assert(f);
8,199✔
167
        assert(ret);
8,199✔
168

169
        for (;;) {
×
170
                pid_t pid;
8,199✔
171

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

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

183
                r = pidref_set_pid(ret, pid);
1,841✔
184
                if (r >= 0)
1,841✔
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) {
17,854✔
210
        _cleanup_free_ char *fs = NULL;
17,854✔
211
        DIR *d;
17,854✔
212
        int r;
17,854✔
213

214
        assert(ret);
17,854✔
215

216
        /* This is not recursive! */
217

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

222
        d = opendir(fs);
17,854✔
223
        if (!d)
17,854✔
224
                return -errno;
11,482✔
225

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

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

234
        FOREACH_DIRENT_ALL(de, d, return -errno) {
305,275✔
235
                if (de->d_type != DT_DIR)
298,737✔
236
                        continue;
284,497✔
237

238
                if (dot_or_dot_dot(de->d_name))
14,240✔
239
                        continue;
13,076✔
240

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

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

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

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

278
        bool done;
17,791✔
279
        do {
17,791✔
280
                _cleanup_fclose_ FILE *f = NULL;
11,482✔
281
                int ret_log_kill;
17,791✔
282

283
                done = true;
17,791✔
284

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

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

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

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

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

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

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

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

330
                                if (ret == 0) {
376✔
331
                                        if (log_kill)
215✔
332
                                                ret = ret_log_kill;
333
                                        else
334
                                                ret = 1;
99✔
335
                                }
336
                        }
337

338
                        done = false;
376✔
339

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

350
        return ret;
351
}
352

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

365
        assert(path);
17,029✔
366
        assert(sig >= 0);
17,029✔
367

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

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

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

381
                return ret;
11,482✔
382
        }
383

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

387
                r = cg_read_subgroup(d, &fn);
5,677✔
388
                if (r < 0) {
5,677✔
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,677✔
393
                        break;
394

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

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

406
        return ret;
5,547✔
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) {
305,469✔
433
        char *t;
305,469✔
434

435
        assert(ret);
305,469✔
436

437
        if (isempty(path))
333,588✔
438
                path = TAKE_PTR(suffix);
439

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

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

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

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

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

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

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

467
        assert(path);
18,823✔
468
        assert(name);
18,823✔
469

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

474
        return lgetxattr_malloc(fs, name, ret, ret_size);
18,823✔
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) {
36,016✔
492
        _cleanup_free_ char *fs = NULL;
36,016✔
493
        int r;
36,016✔
494

495
        assert(path);
36,016✔
496
        assert(name);
36,016✔
497

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

502
        return RET_NERRNO(removexattr(fs, name));
72,032✔
503
}
504

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

510
        assert(pid >= 0);
60,331✔
511
        assert(ret_path);
60,331✔
512

513
        fs = procfs_file_alloca(pid, "cgroup");
66,931✔
514
        r = fopen_unlocked(fs, "re", &f);
60,331✔
515
        if (r == -ENOENT)
60,331✔
516
                return -ESRCH;
517
        if (r < 0)
56,216✔
518
                return r;
519

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

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

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

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

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

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

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

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

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

560
        assert(ret_path);
13,238✔
561

562
        if (!pidref_is_set(pidref))
13,238✔
563
                return -ESRCH;
564
        if (pidref_is_remote(pidref))
26,476✔
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);
13,238✔
572
        if (r < 0)
13,238✔
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);
13,234✔
577
        if (r < 0)
13,234✔
578
                return r;
579

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

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

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

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

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

672
        assert(ret_path);
27,478✔
673

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

678
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
27,478✔
679
        if (e)
27,478✔
680
                *e = 0;
27,442✔
681

682
        *ret_path = p;
27,478✔
683
        return 0;
27,478✔
684
}
685

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

689
        assert(cgroup);
14,027✔
690
        assert(ret_shifted);
14,027✔
691

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

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

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

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

713
        assert(pid >= 0);
17,970✔
714
        assert(ret_cgroup);
17,970✔
715

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

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

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

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

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

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

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

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

745
        if (ret_unit)
34,568✔
746
                return strdup_to(ret_unit, c);
34,568✔
747

748
        return 0;
749
}
750

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

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

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

761
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
63,096✔
762
}
763

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

767
        /* Skips over all slice assignments */
768

769
        for (;;) {
135,918✔
770
                size_t n;
90,064✔
771

772
                p += strspn(p, "/");
90,064✔
773

774
                n = strcspn(p, "/");
90,064✔
775
                if (!valid_slice_name(p, n))
90,064✔
776
                        return p;
44,210✔
777

778
                p += n;
45,854✔
779
        }
780
}
781

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

785
        assert(path);
18,391✔
786

787
        const char *e = skip_slices(path);
18,391✔
788

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

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

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

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

811
                path_simplify(subgroup);
748✔
812

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

816
        if (ret_unit)
18,375✔
817
                *ret_unit = TAKE_PTR(unit);
18,375✔
818

819
        return 0;
820
}
821

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

826
        assert(path);
10,332✔
827
        assert(ret);
10,332✔
828

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

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

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

839
        *ret = TAKE_PTR(path_copy);
10,329✔
840

841
        return 0;
10,329✔
842
}
843

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

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

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

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

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

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

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

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

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

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

884
        if (isempty(p))
15,133✔
885
                return NULL;
886

887
        p += strspn(p, "/");
15,129✔
888

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

893
        const char *s = startswith(p, "session-");
14,933✔
894
        if (!s)
14,933✔
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,487✔
912
        size_t n;
15,487✔
913

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

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

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

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

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

934
        if (type != UNIT_NAME_INSTANCE)
15,289✔
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) &&
587✔
941
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
124✔
942
                return NULL;
114✔
943

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

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

950
        assert(path);
15,487✔
951

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

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

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

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

967
        assert(path);
7,772✔
968

969
        t = skip_user_prefix(path);
7,772✔
970
        if (!t)
7,772✔
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);
200✔
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) {
18✔
990
        int r;
18✔
991

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

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

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

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

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

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

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

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

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

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

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

1042
        assert(path);
9,551✔
1043

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1103
        assert(path);
8,485✔
1104

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

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

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

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

1121
        return 0;
1122
}
1123

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

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

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

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

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

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

1161
        assert(p);
16,429✔
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 (;;) {
50,913✔
1167
                const char *s;
33,671✔
1168
                int n;
33,671✔
1169

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

1176
                e = s;
17,242✔
1177
        }
1178

1179
        if (e)
16,429✔
1180
                return cg_path_decode_unit(e, ret_slice);
16,188✔
1181

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

1185
        return 0;
1186
}
1187

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

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

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

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

1203
        t = skip_user_prefix(p);
7,715✔
1204
        if (!t)
7,715✔
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);
179✔
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) {
27,994✔
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))
27,994✔
1231
                return true;
1232

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

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

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

1242
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
419,700✔
1243
                const char *q;
391,720✔
1244

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

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

1253
        return false;
1254
}
1255

1256
int cg_escape(const char *p, char **ret) {
27,730✔
1257
        _cleanup_free_ char *n = NULL;
27,730✔
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)) {
27,730✔
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);
27,723✔
1275
                if (!n)
27,723✔
1276
                        return -ENOMEM;
1277
        }
1278

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

1283
char* cg_unescape(const char *p) {
108,164✔
1284
        assert(p);
108,164✔
1285

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

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

1292
        return (char*) p;
1293
}
1294

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

1300
        assert(unit);
12,088✔
1301
        assert(ret);
12,088✔
1302

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

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

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

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

1316
        dash = strchr(p, '-');
12,069✔
1317

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

1322
        while (dash) {
12,694✔
1323
                _cleanup_free_ char *escaped = NULL;
630✔
1324
                char n[dash - p + sizeof(".slice")];
630✔
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, '-'))
630✔
1336
                        return -EINVAL;
1337

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

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

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

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

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

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

1359
        *ret = TAKE_PTR(s);
12,064✔
1360
        return 0;
12,064✔
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) {
55,508✔
1388
        _cleanup_free_ char *p = NULL;
55,508✔
1389
        int r;
55,508✔
1390

1391
        assert(attribute);
55,508✔
1392

1393
        r = cg_get_path(path, attribute, &p);
55,508✔
1394
        if (r < 0)
55,508✔
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);
55,508✔
1403
}
1404

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

1409
        assert(attribute);
57,931✔
1410

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

1415
        return read_one_line_file(p, ret);
57,931✔
1416
}
1417

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

1423
        assert(ret);
48,016✔
1424

1425
        r = cg_get_attribute(path, attribute, &value);
48,016✔
1426
        if (r == -ENOENT)
48,016✔
1427
                return -ENODATA;
1428
        if (r < 0)
45,333✔
1429
                return r;
1430

1431
        if (streq(value, "max")) {
45,333✔
1432
                *ret = CGROUP_LIMIT_MAX;
11,295✔
1433
                return 0;
11,295✔
1434
        }
1435

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

1440
        *ret = v;
34,038✔
1441
        return 0;
34,038✔
1442
}
1443

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

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

1454
        return parse_boolean(value);
82✔
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(
28,720✔
1480
                const char *path,
1481
                const char *attribute,
1482
                char * const *keys,
1483
                char **values) {
1484

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

1489
        assert(path);
28,720✔
1490
        assert(attribute);
28,720✔
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);
28,720✔
1499
        if (r < 0)
28,720✔
1500
                return r;
1501

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

1506
        n = strv_length(keys);
25,966✔
1507
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
25,966✔
1508
                return 0;
1509
        assert(strv_is_uniq(keys));
25,966✔
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);
25,966✔
1513
        size_t n_done = 0;
25,966✔
1514

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

1519
                for (i = 0; i < n; i++) {
143,088✔
1520
                        w = first_word(p, keys[i]);
93,183✔
1521
                        if (w)
93,183✔
1522
                                break;
1523
                }
1524

1525
                if (w) {
84,521✔
1526
                        if (v[i]) { /* duplicate entry? */
34,616✔
1527
                                r = -EBADMSG;
×
1528
                                goto fail;
×
1529
                        }
1530

1531
                        size_t l = strcspn(w, NEWLINE);
34,616✔
1532

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

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

1543
                        p = w + l;
8,652✔
1544
                } else
1545
                        p += strcspn(p, NEWLINE);
49,905✔
1546

1547
                p += strspn(p, NEWLINE);
58,557✔
1548
        }
1549

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

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

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

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

1569
        assert(ret);
11,088✔
1570

1571
        if (mask == 0) {
11,088✔
1572
                *ret = NULL;
6,222✔
1573
                return 0;
6,222✔
1574
        }
1575

1576
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
72,990✔
1577
                const char *k;
68,124✔
1578
                size_t l;
68,124✔
1579

1580
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
68,124✔
1581
                        continue;
41,664✔
1582

1583
                k = cgroup_controller_to_string(c);
26,460✔
1584
                l = strlen(k);
26,460✔
1585

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

1589
                if (space)
26,460✔
1590
                        s[n] = ' ';
21,594✔
1591
                memcpy(s + n + space, k, l);
26,460✔
1592
                n += space + l;
26,460✔
1593

1594
                space = true;
26,460✔
1595
        }
1596

1597
        assert(s);
4,866✔
1598

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

1602
        return 0;
4,866✔
1603
}
1604

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

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

1611
        for (;;) {
30,687✔
1612
                _cleanup_free_ char *n = NULL;
26,085✔
1613
                CGroupController v;
30,687✔
1614
                int r;
30,687✔
1615

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

1622
                v = cgroup_controller_from_string(n);
26,085✔
1623
                if (v < 0)
26,085✔
1624
                        continue;
838✔
1625

1626
                m |= CGROUP_CONTROLLER_TO_MASK(v);
25,247✔
1627
        }
1628

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

1633
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
522✔
1634
        CGroupMask mask;
522✔
1635
        int r;
522✔
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;
522✔
1643
        r = cg_get_path(root, "cgroup.controllers", &path);
522✔
1644
        if (r < 0)
522✔
1645
                return r;
1646

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

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

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

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

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

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

1670
        return cg_mask_supported_subtree(root, ret);
265✔
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) {
150✔
1690
        int r;
150✔
1691

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

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

1698
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
143✔
1699
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
143✔
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);
16,174✔
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);
476,307✔
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);
34,265✔
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);
16,833✔
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