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

systemd / systemd / 28557109148

01 Jul 2026 11:57PM UTC coverage: 72.68% (-0.2%) from 72.877%
28557109148

push

github

web-flow
core: assorted hardening fixes flagged by kres (#42840)

2 of 16 new or added lines in 5 files covered. (12.5%)

4972 existing lines in 84 files now uncovered.

341360 of 469675 relevant lines covered (72.68%)

1370997.73 hits per line

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

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

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

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

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

59
        return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
943✔
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));
20✔
84
}
85

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

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

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

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

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

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

112
        assert(ret);
27,163✔
113

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

118
        f = fopen(fs, "re");
27,163✔
119
        if (!f)
27,163✔
120
                return -errno;
15,560✔
121

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

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

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

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

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

146
                        return errno_or_else(EIO);
×
147
                }
148

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

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

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

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

169
        for (;;) {
×
170
                pid_t pid;
13,110✔
171

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

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

183
                r = pidref_set_pid(ret, pid);
1,941✔
184
                if (r >= 0)
1,941✔
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) {
614✔
194
        static thread_local int supported = -1;
614✔
195

196
        if (supported >= 0)
614✔
197
                return supported;
587✔
198

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

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

214
        assert(ret);
27,244✔
215

216
        /* This is not recursive! */
217

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

222
        d = opendir(fs);
27,244✔
223
        if (!d)
27,244✔
224
                return -errno;
15,560✔
225

226
        *ret = d;
11,684✔
227
        return 0;
11,684✔
228
}
229

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

234
        FOREACH_DIRENT_ALL(de, d, return -errno) {
545,083✔
235
                if (de->d_type != DT_DIR)
533,232✔
236
                        continue;
508,297✔
237

238
                if (dot_or_dot_dot(de->d_name))
24,935✔
239
                        continue;
23,702✔
240

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

244
        *ret = NULL;
11,851✔
245
        return 0;
11,851✔
246
}
247

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

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

278
        bool done;
26,690✔
279
        do {
26,690✔
280
                _cleanup_fclose_ FILE *f = NULL;
15,560✔
281
                int ret_log_kill;
26,690✔
282

283
                done = true;
26,690✔
284

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

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

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

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

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

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

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

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

330
                                if (ret == 0) {
219✔
331
                                        if (log_kill)
124✔
332
                                                ret = ret_log_kill;
333
                                        else
334
                                                ret = 1;
89✔
335
                                }
336
                        }
337

338
                        done = false;
219✔
339

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

350
        return ret;
351
}
352

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

365
        assert(path);
26,566✔
366
        assert(sig >= 0);
26,566✔
367

368
        if (!killed_pids) {
26,566✔
369
                killed_pids = allocated_set = set_new(NULL);
25,090✔
370
                if (!killed_pids)
25,090✔
371
                        return -ENOMEM;
372
        }
373

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

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

381
                return ret;
382
        }
383

384
        for (;;) {
11,524✔
385
                _cleanup_free_ char *fn = NULL, *p = NULL;
11,265✔
386

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

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

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

406
        return ret;
11,006✔
407
}
408

409
int cg_kill_kernel_sigkill(const char *path, uint64_t *ret_n_pids_killed) {
614✔
410
        _cleanup_free_ char *killfile = NULL;
614✔
411
        uint64_t n_pids = UINT64_MAX;
614✔
412
        int r;
614✔
413

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

417
        assert(path);
614✔
418

419
        if (!cg_kill_supported())
614✔
420
                return -EOPNOTSUPP;
421

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

426
        if (ret_n_pids_killed) {
614✔
427
                /* This is not and cannot be atomic so there is a chance the counter might not be accurate
428
                 * if a process starts/stops between this read and the next write, but this is used for
429
                 * informational purposes so that's ok. */
UNCOV
430
                r = cg_get_attribute_as_uint64(path, "pids.current", &n_pids);
×
UNCOV
431
                if (r < 0)
×
432
                        log_debug_errno(r, "Failed to read pids.current from cgroup '%s', ignoring: %m", path);
×
433
        }
434

435
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
614✔
436
        if (r < 0)
614✔
437
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
438

439
        if (ret_n_pids_killed)
614✔
UNCOV
440
                *ret_n_pids_killed = n_pids;
×
441

442
        return 0;
443
}
444

445
int cg_get_path(const char *path, const char *suffix, char **ret) {
603,945✔
446
        char *t;
603,945✔
447

448
        assert(ret);
603,945✔
449

450
        if (isempty(path))
659,514✔
451
                path = TAKE_PTR(suffix);
452

453
        t = path_join("/sys/fs/cgroup", path, suffix);
603,945✔
454
        if (!t)
603,945✔
455
                return -ENOMEM;
456

457
        *ret = path_simplify(t);
603,945✔
458
        return 0;
603,945✔
459
}
460

461
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
21,662✔
462
        _cleanup_free_ char *fs = NULL;
21,662✔
463
        int r;
21,662✔
464

465
        assert(path);
21,662✔
466
        assert(name);
21,662✔
467
        assert(value || size <= 0);
21,662✔
468

469
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
21,662✔
470
        if (r < 0)
21,662✔
471
                return r;
472

473
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
21,662✔
474
}
475

476
int cg_get_xattr(const char *path, const char *name, char **ret, size_t *ret_size) {
31,950✔
477
        _cleanup_free_ char *fs = NULL;
31,950✔
478
        int r;
31,950✔
479

480
        assert(path);
31,950✔
481
        assert(name);
31,950✔
482

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

487
        return lgetxattr_malloc(fs, name, ret, ret_size);
31,950✔
488
}
489

490
int cg_get_xattr_bool(const char *path, const char *name) {
176✔
491
        _cleanup_free_ char *fs = NULL;
176✔
492
        int r;
176✔
493

494
        assert(path);
176✔
495
        assert(name);
176✔
496

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

501
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
176✔
502
}
503

504
int cg_remove_xattr(const char *path, const char *name) {
83,175✔
505
        _cleanup_free_ char *fs = NULL;
83,175✔
506
        int r;
83,175✔
507

508
        assert(path);
83,175✔
509
        assert(name);
83,175✔
510

511
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
83,175✔
512
        if (r < 0)
83,175✔
513
                return r;
514

515
        return RET_NERRNO(removexattr(fs, name));
166,350✔
516
}
517

518
int cg_pid_get_path(pid_t pid, char **ret_path) {
110,353✔
519
        _cleanup_fclose_ FILE *f = NULL;
110,353✔
520
        const char *fs;
110,353✔
521
        int r;
110,353✔
522

523
        assert(pid >= 0);
110,353✔
524
        assert(ret_path);
110,353✔
525

526
        fs = procfs_file_alloca(pid, "cgroup");
124,185✔
527
        r = fopen_unlocked(fs, "re", &f);
110,353✔
528
        if (r == -ENOENT)
110,353✔
529
                return -ESRCH;
530
        if (r < 0)
105,618✔
531
                return r;
532

533
        for (;;) {
105,618✔
534
                _cleanup_free_ char *line = NULL;
105,618✔
535
                char *e;
105,618✔
536

537
                r = read_line(f, LONG_LINE_MAX, &line);
105,618✔
538
                if (r < 0)
105,618✔
539
                        return r;
540
                if (r == 0)
105,610✔
541
                        return -ENODATA;
542

543
                e = startswith(line, "0:");
105,610✔
544
                if (!e)
105,610✔
545
                        continue;
×
546

547
                e = strchr(e, ':');
105,610✔
548
                if (!e)
105,610✔
549
                        continue;
×
550

551
                _cleanup_free_ char *path = strdup(e + 1);
105,610✔
552
                if (!path)
105,610✔
553
                        return -ENOMEM;
554

555
                /* Refuse cgroup paths from outside our cgroup namespace */
556
                if (startswith(path, "/../"))
105,610✔
557
                        return -EUNATCH;
558

559
                /* Truncate suffix indicating the process is a zombie */
560
                e = endswith(path, " (deleted)");
105,610✔
561
                if (e)
105,610✔
562
                        *e = 0;
194✔
563

564
                *ret_path = TAKE_PTR(path);
105,610✔
565
                return 0;
105,610✔
566
        }
567
}
568

569
int cg_pidref_get_path(const PidRef *pidref, char **ret_path) {
36,614✔
570
        _cleanup_free_ char *path = NULL;
36,614✔
571
        int r;
36,614✔
572

573
        assert(ret_path);
36,614✔
574

575
        if (!pidref_is_set(pidref))
36,614✔
576
                return -ESRCH;
577
        if (pidref_is_remote(pidref))
73,228✔
578
                return -EREMOTE;
579

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

584
        r = cg_pid_get_path(pidref->pid, &path);
36,614✔
585
        if (r < 0)
36,614✔
586
                return r;
587

588
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
589
        r = pidref_verify(pidref);
35,503✔
590
        if (r < 0)
35,503✔
591
                return r;
592

593
        *ret_path = TAKE_PTR(path);
35,503✔
594
        return 0;
35,503✔
595
}
596

597
int cg_is_empty(const char *path) {
5,141✔
598
        _cleanup_free_ char *t = NULL;
5,141✔
599
        int r;
5,141✔
600

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

604
        assert(path);
5,141✔
605

606
        /* The root cgroup is always populated */
607
        if (empty_or_root(path))
5,141✔
608
                return false;
609

610
        r = cg_get_keyed_attribute(path, "cgroup.events", STRV_MAKE("populated"), &t);
5,141✔
611
        if (r == -ENOENT)
5,141✔
612
                return true;
613
        if (r < 0)
817✔
614
                return r;
615

616
        return streq(t, "0");
817✔
617
}
618

619
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
19✔
620
        _cleanup_free_ char *controller = NULL;
19✔
621
        const char *path;
19✔
622
        int r;
19✔
623

624
        assert(spec);
19✔
625

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

629
        if (isempty(spec) || path_is_absolute(spec)) {
19✔
630
                /* Assume this does not contain controller. */
631
                path = spec;
12✔
632
                goto finalize;
12✔
633
        }
634

635
        const char *e = strchr(spec, ':');
7✔
636
        if (!e) {
7✔
637
                /* Controller only. */
638
                if (ret_controller) {
1✔
639
                        controller = strdup(spec);
1✔
640
                        if (!controller)
1✔
641
                                return -ENOMEM;
642
                }
643

644
                path = NULL;
645
        } else {
646
                /* Both controller and path. */
647
                if (ret_controller) {
6✔
648
                        controller = strndup(spec, e - spec);
6✔
649
                        if (!controller)
6✔
650
                                return -ENOMEM;
651
                }
652

653
                path = e + 1;
6✔
654
        }
655

656
finalize:
12✔
657
        path = empty_to_null(path);
18✔
658

659
        if (path) {
15✔
660
                /* Non-empty path must be absolute. */
661
                if (!path_is_absolute(path))
15✔
662
                        return -EINVAL;
663

664
                /* Path must not contain dot-dot. */
665
                if (!path_is_safe(path))
14✔
666
                        return -EINVAL;
667
        }
668

669
        if (ret_path) {
18✔
670
                r = path_simplify_alloc(path, ret_path);
18✔
671
                if (r < 0)
18✔
672
                        return r;
673
        }
674

675
        if (ret_controller)
18✔
676
                *ret_controller = TAKE_PTR(controller);
18✔
677

678
        return 0;
679
}
680

681
int cg_get_root_path(char **ret_path) {
49,969✔
682
        char *p, *e;
49,969✔
683
        int r;
49,969✔
684

685
        assert(ret_path);
49,969✔
686

687
        r = cg_pid_get_path(1, &p);
49,969✔
688
        if (r < 0)
49,969✔
689
                return r;
49,969✔
690

691
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
49,969✔
692
        if (e)
49,969✔
693
                *e = 0;
49,925✔
694

695
        *ret_path = p;
49,969✔
696
        return 0;
49,969✔
697
}
698

699
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
16,875✔
700
        int r;
16,875✔
701

702
        assert(cgroup);
16,875✔
703
        assert(ret_shifted);
16,875✔
704

705
        _cleanup_free_ char *rt = NULL;
16,875✔
706
        if (!root) {
16,875✔
707
                /* If the root was specified let's use that, otherwise
708
                 * let's determine it from PID 1 */
709

710
                r = cg_get_root_path(&rt);
3,751✔
711
                if (r < 0)
3,751✔
712
                        return r;
713

714
                root = rt;
3,751✔
715
        }
716

717
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
16,875✔
718
        return 0;
16,875✔
719
}
720

721
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
20,325✔
722
        _cleanup_free_ char *raw = NULL;
20,325✔
723
        const char *c;
20,325✔
724
        int r;
20,325✔
725

726
        assert(pid >= 0);
20,325✔
727
        assert(ret_cgroup);
20,325✔
728

729
        r = cg_pid_get_path(pid, &raw);
20,325✔
730
        if (r < 0)
20,325✔
731
                return r;
732

733
        r = cg_shift_path(raw, root, &c);
16,693✔
734
        if (r < 0)
16,693✔
735
                return r;
736

737
        if (c == raw) {
16,693✔
738
                *ret_cgroup = TAKE_PTR(raw);
16,693✔
739
                return 0;
16,693✔
740
        }
741

742
        return strdup_to(ret_cgroup, c);
×
743
}
744

745
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
42,043✔
746
        assert(cgroup);
42,043✔
747

748
        size_t n = strcspn(cgroup, "/");
42,043✔
749
        if (n < 3)
42,043✔
750
                return -ENXIO;
751

752
        char *c = strndupa_safe(cgroup, n);
42,028✔
753
        c = cg_unescape(c);
42,028✔
754

755
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
42,028✔
756
                return -ENXIO;
757

758
        if (ret_unit)
42,019✔
759
                return strdup_to(ret_unit, c);
42,019✔
760

761
        return 0;
762
}
763

764
static bool valid_slice_name(const char *p, size_t n) {
151,829✔
765
        assert(p || n == 0);
151,829✔
766

767
        if (n < STRLEN("x.slice"))
151,829✔
768
                return false;
769

770
        char *c = strndupa_safe(p, n);
151,782✔
771
        if (!endswith(c, ".slice"))
151,782✔
772
                return false;
773

774
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
77,401✔
775
}
776

777
static const char* skip_slices(const char *p) {
54,320✔
778
        assert(p);
54,320✔
779

780
        /* Skips over all slice assignments */
781

782
        for (;;) {
167,134✔
783
                size_t n;
110,727✔
784

785
                p += strspn(p, "/");
110,727✔
786

787
                n = strcspn(p, "/");
110,727✔
788
                if (!valid_slice_name(p, n))
110,727✔
789
                        return p;
54,320✔
790

791
                p += n;
56,407✔
792
        }
793
}
794

795
int cg_path_get_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
22,253✔
796
        int r;
22,253✔
797

798
        assert(path);
22,253✔
799

800
        const char *e = skip_slices(path);
22,253✔
801

802
        _cleanup_free_ char *unit = NULL;
22,253✔
803
        r = cg_path_decode_unit(e, &unit);
22,253✔
804
        if (r < 0)
22,253✔
805
                return r;
806

807
        /* We skipped over the slices, don't accept any now */
808
        if (endswith(unit, ".slice"))
22,233✔
809
                return -ENXIO;
810

811
        if (ret_subgroup) {
22,233✔
812
                _cleanup_free_ char *subgroup = NULL;
×
813
                e += strcspn(e, "/");
826✔
814
                e += strspn(e, "/");
826✔
815

816
                if (isempty(e))
826✔
817
                        subgroup = NULL;
818
                else {
819
                        subgroup = strdup(e);
271✔
820
                        if (!subgroup)
271✔
821
                                return -ENOMEM;
×
822
                }
823

824
                path_simplify(subgroup);
826✔
825

826
                *ret_subgroup = TAKE_PTR(subgroup);
826✔
827
        }
828

829
        if (ret_unit)
22,233✔
830
                *ret_unit = TAKE_PTR(unit);
22,233✔
831

832
        return 0;
833
}
834

835
int cg_path_get_unit_path(const char *path, char **ret) {
12,958✔
836
        _cleanup_free_ char *path_copy = NULL;
12,958✔
837
        char *unit_name;
12,958✔
838

839
        assert(path);
12,958✔
840
        assert(ret);
12,958✔
841

842
        path_copy = strdup(path);
12,958✔
843
        if (!path_copy)
12,958✔
844
                return -ENOMEM;
845

846
        unit_name = (char*) skip_slices(path_copy);
12,958✔
847
        unit_name[strcspn(unit_name, "/")] = 0;
12,958✔
848

849
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
12,958✔
850
                return -ENXIO;
851

852
        *ret = TAKE_PTR(path_copy);
12,955✔
853

854
        return 0;
12,955✔
855
}
856

857
int cg_pid_get_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
921✔
858
        int r;
921✔
859

860
        _cleanup_free_ char *cgroup = NULL;
921✔
861
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
921✔
862
        if (r < 0)
921✔
863
                return r;
864

865
        return cg_path_get_unit_full(cgroup, ret_unit, ret_subgroup);
921✔
866
}
867

868
int cg_pidref_get_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
808✔
869
        int r;
808✔
870

871
        if (!pidref_is_set(pidref))
808✔
872
                return -ESRCH;
808✔
873
        if (pidref_is_remote(pidref))
1,616✔
874
                return -EREMOTE;
875

876
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
808✔
877
        r = cg_pid_get_unit_full(pidref->pid, &unit, &subgroup);
808✔
878
        if (r < 0)
808✔
879
                return r;
880

881
        r = pidref_verify(pidref);
808✔
882
        if (r < 0)
808✔
883
                return r;
884

885
        if (ret_unit)
808✔
886
                *ret_unit = TAKE_PTR(unit);
808✔
887
        if (ret_subgroup)
808✔
888
                *ret_subgroup = TAKE_PTR(subgroup);
66✔
889
        return 0;
890
}
891

892
static const char* skip_session(const char *p) {
18,670✔
893
        size_t n;
18,670✔
894

895
        /* Skip session-*.scope, but require it to be there. */
896

897
        if (isempty(p))
18,670✔
898
                return NULL;
899

900
        p += strspn(p, "/");
18,666✔
901

902
        n = strcspn(p, "/");
18,666✔
903
        if (n < STRLEN("session-x.scope"))
18,666✔
904
                return NULL;
905

906
        const char *s = startswith(p, "session-");
18,410✔
907
        if (!s)
18,410✔
908
                return NULL;
909

910
        /* Note that session scopes never need unescaping, since they cannot conflict with the kernel's
911
         * own names, hence we don't need to call cg_unescape() here. */
912
        char *f = strndupa_safe(s, p + n - s),
33✔
913
             *e = endswith(f, ".scope");
33✔
914
        if (!e)
33✔
915
                return NULL;
916
        *e = '\0';
33✔
917

918
        if (!session_id_valid(f))
33✔
919
                return NULL;
920

921
        return skip_leading_slash(p + n);
33✔
922
}
923

924
static const char* skip_user_manager(const char *p) {
19,109✔
925
        size_t n;
19,109✔
926

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

929
        if (isempty(p))
19,109✔
930
                return NULL;
19,109✔
931

932
        p += strspn(p, "/");
19,105✔
933

934
        n = strcspn(p, "/");
19,105✔
935
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
19,105✔
936
                return NULL;
937

938
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
939
         * resolve user/capsule name. */
940
        _cleanup_free_ char *unit_name = strndup(p, n);
18,849✔
941
        if (!unit_name)
18,849✔
942
                return NULL;
943

944
        _cleanup_free_ char *i = NULL;
18,849✔
945
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
18,849✔
946

947
        if (type != UNIT_NAME_INSTANCE)
18,849✔
948
                return NULL;
949

950
        /* Note that user manager services never need unescaping, since they cannot conflict with the
951
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
952
         * instance names, they should be always valid as we validate them upon unit start. */
953
        if (!(startswith(unit_name, "user@") && parse_uid(i, NULL) >= 0) &&
800✔
954
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
188✔
955
                return NULL;
956

957
        return skip_leading_slash(p + n);
439✔
958
}
959

960
static const char* skip_user_prefix(const char *path) {
19,109✔
961
        const char *e, *t;
19,109✔
962

963
        assert(path);
19,109✔
964

965
        /* Skip slices, if there are any */
966
        e = skip_slices(path);
19,109✔
967

968
        /* Skip the user manager, if it's in the path now... */
969
        t = skip_user_manager(e);
19,109✔
970
        if (t)
19,109✔
971
                return t;
972

973
        /* Alternatively skip the user session if it is in the path... */
974
        return skip_session(e);
18,670✔
975
}
976

977
int cg_path_get_user_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
9,581✔
978
        const char *t;
9,581✔
979

980
        assert(path);
9,581✔
981

982
        t = skip_user_prefix(path);
9,581✔
983
        if (!t)
9,581✔
984
                return -ENXIO;
985

986
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
987
         * parser. */
988
        return cg_path_get_unit_full(t, ret_unit, ret_subgroup);
244✔
989
}
990

991
int cg_pid_get_user_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
54✔
992
        int r;
54✔
993

994
        _cleanup_free_ char *cgroup = NULL;
54✔
995
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
54✔
996
        if (r < 0)
54✔
997
                return r;
998

999
        return cg_path_get_user_unit_full(cgroup, ret_unit, ret_subgroup);
54✔
1000
}
1001

1002
int cg_pidref_get_user_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
13✔
1003
        int r;
13✔
1004

1005
        if (!pidref_is_set(pidref))
13✔
1006
                return -ESRCH;
13✔
1007
        if (pidref_is_remote(pidref))
26✔
1008
                return -EREMOTE;
1009

1010
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
13✔
1011
        r = cg_pid_get_user_unit_full(pidref->pid, &unit, &subgroup);
13✔
1012
        if (r < 0)
13✔
1013
                return r;
1014

1015
        r = pidref_verify(pidref);
4✔
1016
        if (r < 0)
4✔
1017
                return r;
1018

1019
        if (ret_unit)
4✔
1020
                *ret_unit = TAKE_PTR(unit);
4✔
1021
        if (ret_subgroup)
4✔
1022
                *ret_subgroup = TAKE_PTR(subgroup);
×
1023
        return 0;
1024
}
1025

1026
int cg_path_get_machine_name(const char *path, char **ret_machine) {
39✔
1027
        _cleanup_free_ char *u = NULL;
39✔
1028
        const char *sl;
39✔
1029
        int r;
39✔
1030

1031
        r = cg_path_get_unit(path, &u);
39✔
1032
        if (r < 0)
39✔
1033
                return r;
1034

1035
        sl = strjoina("/run/systemd/machines/unit:", u);
195✔
1036
        return readlink_malloc(sl, ret_machine);
39✔
1037
}
1038

1039
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
39✔
1040
        _cleanup_free_ char *cgroup = NULL;
39✔
1041
        int r;
39✔
1042

1043
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
39✔
1044
        if (r < 0)
39✔
1045
                return r;
1046

1047
        return cg_path_get_machine_name(cgroup, ret_machine);
39✔
1048
}
1049

1050
int cg_path_get_session(const char *path, char **ret_session) {
11,451✔
1051
        _cleanup_free_ char *unit = NULL;
11,451✔
1052
        char *start, *end;
11,451✔
1053
        int r;
11,451✔
1054

1055
        assert(path);
11,451✔
1056

1057
        r = cg_path_get_unit(path, &unit);
11,451✔
1058
        if (r < 0)
11,451✔
1059
                return r;
1060

1061
        start = startswith(unit, "session-");
11,450✔
1062
        if (!start)
11,450✔
1063
                return -ENXIO;
1064
        end = endswith(start, ".scope");
542✔
1065
        if (!end)
542✔
1066
                return -ENXIO;
1067

1068
        *end = 0;
542✔
1069
        if (!session_id_valid(start))
542✔
1070
                return -ENXIO;
1071

1072
        if (!ret_session)
541✔
1073
                return 0;
1074

1075
        return strdup_to(ret_session, start);
541✔
1076
}
1077

1078
int cg_pid_get_session(pid_t pid, char **ret_session) {
1,861✔
1079
        _cleanup_free_ char *cgroup = NULL;
1,861✔
1080
        int r;
1,861✔
1081

1082
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1,861✔
1083
        if (r < 0)
1,861✔
1084
                return r;
1085

1086
        return cg_path_get_session(cgroup, ret_session);
1,861✔
1087
}
1088

1089
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
496✔
1090
        int r;
496✔
1091

1092
        if (!pidref_is_set(pidref))
496✔
1093
                return -ESRCH;
496✔
1094
        if (pidref_is_remote(pidref))
992✔
1095
                return -EREMOTE;
1096

1097
        _cleanup_free_ char *session = NULL;
496✔
1098
        r = cg_pid_get_session(pidref->pid, &session);
496✔
1099
        if (r < 0)
496✔
1100
                return r;
1101

1102
        r = pidref_verify(pidref);
445✔
1103
        if (r < 0)
445✔
1104
                return r;
1105

1106
        if (ret)
445✔
1107
                *ret = TAKE_PTR(session);
445✔
1108
        return 0;
1109
}
1110

1111
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
10,301✔
1112
        _cleanup_free_ char *slice = NULL;
10,301✔
1113
        char *start, *end;
10,301✔
1114
        int r;
10,301✔
1115

1116
        assert(path);
10,301✔
1117

1118
        r = cg_path_get_slice(path, &slice);
10,301✔
1119
        if (r < 0)
10,301✔
1120
                return r;
1121

1122
        start = startswith(slice, "user-");
10,301✔
1123
        if (!start)
10,301✔
1124
                return -ENXIO;
1125

1126
        end = endswith(start, ".slice");
840✔
1127
        if (!end)
840✔
1128
                return -ENXIO;
1129

1130
        *end = 0;
840✔
1131
        if (parse_uid(start, ret_uid) < 0)
840✔
1132
                return -ENXIO;
×
1133

1134
        return 0;
1135
}
1136

1137
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
765✔
1138
        _cleanup_free_ char *cgroup = NULL;
765✔
1139
        int r;
765✔
1140

1141
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
765✔
1142
        if (r < 0)
765✔
1143
                return r;
1144

1145
        return cg_path_get_owner_uid(cgroup, ret_uid);
765✔
1146
}
1147

1148
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
51✔
1149
        int r;
51✔
1150

1151
        if (!pidref_is_set(pidref))
51✔
1152
                return -ESRCH;
51✔
1153
        if (pidref_is_remote(pidref))
51✔
1154
                return -EREMOTE;
1155

1156
        uid_t uid;
51✔
1157
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
51✔
1158
        if (r < 0)
51✔
1159
                return r;
1160

1161
        r = pidref_verify(pidref);
12✔
1162
        if (r < 0)
12✔
1163
                return r;
1164

1165
        if (ret)
12✔
1166
                *ret = uid;
12✔
1167

1168
        return 0;
1169
}
1170

1171
int cg_path_get_slice(const char *p, char **ret_slice) {
20,108✔
1172
        const char *e = NULL;
20,108✔
1173

1174
        assert(p);
20,108✔
1175

1176
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1177
         * the first non-slice unit. */
1178

1179
        for (;;) {
62,096✔
1180
                const char *s;
41,102✔
1181
                int n;
41,102✔
1182

1183
                n = path_find_first_component(&p, /* accept_dot_dot= */ false, &s);
41,102✔
1184
                if (n < 0)
41,102✔
1185
                        return n;
×
1186
                if (!valid_slice_name(s, n))
41,102✔
1187
                        break;
1188

1189
                e = s;
20,994✔
1190
        }
1191

1192
        if (e)
20,108✔
1193
                return cg_path_decode_unit(e, ret_slice);
19,781✔
1194

1195
        if (ret_slice)
327✔
1196
                return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
327✔
1197

1198
        return 0;
1199
}
1200

1201
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
58✔
1202
        _cleanup_free_ char *cgroup = NULL;
58✔
1203
        int r;
58✔
1204

1205
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
58✔
1206
        if (r < 0)
58✔
1207
                return r;
1208

1209
        return cg_path_get_slice(cgroup, ret_slice);
58✔
1210
}
1211

1212
int cg_path_get_user_slice(const char *p, char **ret_slice) {
9,528✔
1213
        const char *t;
9,528✔
1214
        assert(p);
9,528✔
1215

1216
        t = skip_user_prefix(p);
9,528✔
1217
        if (!t)
9,528✔
1218
                return -ENXIO;
1219

1220
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1221
         * from here on. */
1222
        return cg_path_get_slice(t, ret_slice);
228✔
1223
}
1224

1225
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1226
        _cleanup_free_ char *cgroup = NULL;
1✔
1227
        int r;
1✔
1228

1229
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1✔
1230
        if (r < 0)
1✔
1231
                return r;
1232

1233
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1234
}
1235

1236
bool cg_needs_escape(const char *p) {
40,599✔
1237

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

1243
        if (!filename_is_valid(p))
40,599✔
1244
                return true;
1245

1246
        if (IN_SET(p[0], '_', '.'))
40,595✔
1247
                return true;
1248

1249
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
40,589✔
1250
                return true;
2✔
1251

1252
        if (startswith(p, "cgroup."))
40,587✔
1253
                return true;
1254

1255
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
608,775✔
1256
                const char *q;
568,190✔
1257

1258
                q = startswith(p, cgroup_controller_to_string(c));
568,190✔
1259
                if (!q)
568,190✔
1260
                        continue;
568,190✔
1261

1262
                if (q[0] == '.')
×
1263
                        return true;
1264
        }
1265

1266
        return false;
1267
}
1268

1269
int cg_escape(const char *p, char **ret) {
39,951✔
1270
        _cleanup_free_ char *n = NULL;
39,951✔
1271

1272
        assert(ret);
39,951✔
1273

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

1279
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1280

1281
        if (cg_needs_escape(p)) {
39,951✔
1282
                n = strjoin("_", p);
7✔
1283
                if (!n)
7✔
1284
                        return -ENOMEM;
1285

1286
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1287
                        return -EINVAL;
1288
        } else {
1289
                n = strdup(p);
39,944✔
1290
                if (!n)
39,944✔
1291
                        return -ENOMEM;
1292
        }
1293

1294
        *ret = TAKE_PTR(n);
39,951✔
1295
        return 0;
39,951✔
1296
}
1297

1298
char* cg_unescape(const char *p) {
132,547✔
1299
        assert(p);
132,547✔
1300

1301
        /* The return value of this function (unlike cg_escape())
1302
         * doesn't need free()! */
1303

1304
        if (p[0] == '_')
132,547✔
1305
                return (char*) p+1;
14✔
1306

1307
        return (char*) p;
1308
}
1309

1310
int cg_slice_to_path(const char *unit, char **ret) {
17,775✔
1311
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
17,775✔
1312
        const char *dash;
17,775✔
1313
        int r;
17,775✔
1314

1315
        assert(unit);
17,775✔
1316
        assert(ret);
17,775✔
1317

1318
        if (streq(unit, SPECIAL_ROOT_SLICE))
17,775✔
1319
                return strdup_to(ret, "");
7✔
1320

1321
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
17,768✔
1322
                return -EINVAL;
1323

1324
        if (!endswith(unit, ".slice"))
17,757✔
1325
                return -EINVAL;
1326

1327
        r = unit_name_to_prefix(unit, &p);
17,756✔
1328
        if (r < 0)
17,756✔
1329
                return r;
1330

1331
        dash = strchr(p, '-');
17,756✔
1332

1333
        /* Don't allow initial dashes */
1334
        if (dash == p)
17,756✔
1335
                return -EINVAL;
1336

1337
        while (dash) {
18,955✔
1338
                _cleanup_free_ char *escaped = NULL;
1,204✔
1339
                char n[dash - p + sizeof(".slice")];
1,204✔
1340

1341
#if HAS_FEATURE_MEMORY_SANITIZER
1342
                /* msan doesn't instrument stpncpy, so it thinks
1343
                 * n is later used uninitialized:
1344
                 * https://github.com/google/sanitizers/issues/926
1345
                 */
1346
                zero(n);
1347
#endif
1348

1349
                /* Don't allow trailing or double dashes */
1350
                if (IN_SET(dash[1], 0, '-'))
1,204✔
1351
                        return -EINVAL;
1352

1353
                strcpy(stpncpy(n, p, dash - p), ".slice");
1,202✔
1354
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
1,202✔
1355
                        return -EINVAL;
1356

1357
                r = cg_escape(n, &escaped);
1,202✔
1358
                if (r < 0)
1,202✔
1359
                        return r;
1360

1361
                if (!strextend(&s, escaped, "/"))
1,202✔
1362
                        return -ENOMEM;
1363

1364
                dash = strchr(dash+1, '-');
1,202✔
1365
        }
1366

1367
        r = cg_escape(unit, &e);
17,751✔
1368
        if (r < 0)
17,751✔
1369
                return r;
1370

1371
        if (!strextend(&s, e))
17,751✔
1372
                return -ENOMEM;
1373

1374
        *ret = TAKE_PTR(s);
17,751✔
1375
        return 0;
17,751✔
1376
}
1377

1378
int cg_is_threaded(const char *path) {
×
1379
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
1380
        _cleanup_strv_free_ char **v = NULL;
×
1381
        int r;
×
1382

1383
        r = cg_get_path(path, "cgroup.type", &fs);
×
1384
        if (r < 0)
×
1385
                return r;
1386

1387
        r = read_full_virtual_file(fs, &contents, NULL);
×
1388
        if (r == -ENOENT)
×
1389
                return false; /* Assume no. */
1390
        if (r < 0)
×
1391
                return r;
1392

1393
        v = strv_split(contents, NULL);
×
1394
        if (!v)
×
1395
                return -ENOMEM;
1396

1397
        /* If the cgroup is in the threaded mode, it contains "threaded".
1398
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
1399
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1400
}
1401

1402
int cg_set_attribute(const char *path, const char *attribute, const char *value) {
122,361✔
1403
        _cleanup_free_ char *p = NULL;
122,361✔
1404
        int r;
122,361✔
1405

1406
        assert(attribute);
122,361✔
1407

1408
        r = cg_get_path(path, attribute, &p);
122,361✔
1409
        if (r < 0)
122,361✔
1410
                return r;
1411

1412
        /* https://lore.kernel.org/all/20250419183545.1982187-1-shakeel.butt@linux.dev/ adds O_NONBLOCK
1413
         * semantics to memory.max and memory.high to skip synchronous memory reclaim when O_NONBLOCK is
1414
         * enabled. Let's always open cgroupv2 attribute files in nonblocking mode to immediately take
1415
         * advantage of this and any other asynchronous resource reclaim that's added to the cgroupv2 API in
1416
         * the future. */
1417
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER|WRITE_STRING_FILE_OPEN_NONBLOCKING);
122,361✔
1418
}
1419

1420
int cg_get_attribute(const char *path, const char *attribute, char **ret) {
117,128✔
1421
        _cleanup_free_ char *p = NULL;
117,128✔
1422
        int r;
117,128✔
1423

1424
        assert(attribute);
117,128✔
1425

1426
        r = cg_get_path(path, attribute, &p);
117,128✔
1427
        if (r < 0)
117,128✔
1428
                return r;
1429

1430
        return read_one_line_file(p, ret);
117,128✔
1431
}
1432

1433
int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t *ret) {
98,313✔
1434
        _cleanup_free_ char *value = NULL;
98,313✔
1435
        uint64_t v;
98,313✔
1436
        int r;
98,313✔
1437

1438
        assert(ret);
98,313✔
1439

1440
        r = cg_get_attribute(path, attribute, &value);
98,313✔
1441
        if (r == -ENOENT)
98,313✔
1442
                return -ENODATA;
1443
        if (r < 0)
94,854✔
1444
                return r;
1445

1446
        if (streq(value, "max")) {
94,854✔
1447
                *ret = CGROUP_LIMIT_MAX;
23,888✔
1448
                return 0;
23,888✔
1449
        }
1450

1451
        r = safe_atou64(value, &v);
70,966✔
1452
        if (r < 0)
70,966✔
1453
                return r;
1454

1455
        *ret = v;
70,966✔
1456
        return 0;
70,966✔
1457
}
1458

UNCOV
1459
int cg_get_attribute_as_bool(const char *path, const char *attribute) {
×
UNCOV
1460
        _cleanup_free_ char *value = NULL;
×
UNCOV
1461
        int r;
×
1462

UNCOV
1463
        r = cg_get_attribute(path, attribute, &value);
×
UNCOV
1464
        if (r == -ENOENT)
×
1465
                return -ENODATA;
UNCOV
1466
        if (r < 0)
×
1467
                return r;
1468

UNCOV
1469
        return parse_boolean(value);
×
1470
}
1471

1472
int cg_get_owner(const char *path, uid_t *ret_uid) {
24✔
1473
        _cleanup_free_ char *f = NULL;
24✔
1474
        struct stat stats;
24✔
1475
        int r;
24✔
1476

1477
        assert(ret_uid);
24✔
1478

1479
        r = cg_get_path(path, /* suffix= */ NULL, &f);
24✔
1480
        if (r < 0)
24✔
1481
                return r;
1482

1483
        if (stat(f, &stats) < 0)
24✔
1484
                return -errno;
16✔
1485

1486
        r = stat_verify_directory(&stats);
8✔
1487
        if (r < 0)
8✔
1488
                return r;
1489

1490
        *ret_uid = stats.st_uid;
8✔
1491
        return 0;
8✔
1492
}
1493

1494
int cg_get_keyed_attribute(
59,150✔
1495
                const char *path,
1496
                const char *attribute,
1497
                char * const *keys,
1498
                char **values) {
1499

1500
        _cleanup_free_ char *filename = NULL, *contents = NULL;
59,150✔
1501
        size_t n;
59,150✔
1502
        int r;
59,150✔
1503

1504
        assert(path);
59,150✔
1505
        assert(attribute);
59,150✔
1506

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

1513
        r = cg_get_path(path, attribute, &filename);
59,150✔
1514
        if (r < 0)
59,150✔
1515
                return r;
1516

1517
        r = read_full_file(filename, &contents, /* ret_size= */ NULL);
59,150✔
1518
        if (r < 0)
59,150✔
1519
                return r;
1520

1521
        n = strv_length(keys);
54,755✔
1522
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
54,755✔
1523
                return 0;
1524
        assert(strv_is_uniq(keys));
54,755✔
1525

1526
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1527
        char **v = newa0(char*, n);
54,755✔
1528
        size_t n_done = 0;
54,755✔
1529

1530
        for (const char *p = contents; *p;) {
174,107✔
1531
                const char *w;
1532
                size_t i;
1533

1534
                for (i = 0; i < n; i++) {
293,467✔
1535
                        w = first_word(p, keys[i]);
191,195✔
1536
                        if (w)
191,195✔
1537
                                break;
1538
                }
1539

1540
                if (w) {
174,105✔
1541
                        if (v[i]) { /* duplicate entry? */
71,833✔
1542
                                r = -EBADMSG;
×
1543
                                goto fail;
×
1544
                        }
1545

1546
                        size_t l = strcspn(w, NEWLINE);
71,833✔
1547

1548
                        v[i] = strndup(w, l);
71,833✔
1549
                        if (!v[i]) {
71,833✔
1550
                                r = -ENOMEM;
×
1551
                                goto fail;
×
1552
                        }
1553

1554
                        n_done++;
71,833✔
1555
                        if (n_done >= n)
71,833✔
1556
                                break;
1557

1558
                        p = w + l;
17,080✔
1559
                } else
1560
                        p += strcspn(p, NEWLINE);
102,272✔
1561

1562
                p += strspn(p, NEWLINE);
119,352✔
1563
        }
1564

1565
        if (n_done < n) {
54,755✔
1566
                r = -ENXIO;
2✔
1567
                goto fail;
2✔
1568
        }
1569

1570
        memcpy(values, v, sizeof(char*) * n);
54,753✔
1571
        return 0;
54,753✔
1572

1573
fail:
2✔
1574
        free_many_charp(v, n);
59,152✔
1575
        return r;
1576
}
1577

1578
int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret) {
36,902✔
1579
        _cleanup_free_ char *val = NULL;
36,902✔
1580
        int r;
36,902✔
1581

1582
        assert(key);
36,902✔
1583
        assert(ret);
36,902✔
1584

1585
        r = cg_get_keyed_attribute(path, attribute, STRV_MAKE(key), &val);
36,902✔
1586
        if (r < 0)
36,902✔
1587
                return r;
1588

1589
        r = safe_atou64(val, ret);
36,858✔
1590
        if (r < 0)
36,858✔
1591
                return log_debug_errno(r, "Failed to parse value '%s' of key '%s' in cgroup attribute '%s': %m", val, key, attribute);
×
1592

1593
        return 0;
1594
}
1595

1596
int cg_mask_to_string(CGroupMask mask, char **ret) {
24,864✔
1597
        _cleanup_free_ char *s = NULL;
24,864✔
1598
        bool space = false;
24,864✔
1599
        CGroupController c;
24,864✔
1600
        size_t n = 0;
24,864✔
1601

1602
        assert(ret);
24,864✔
1603

1604
        if (mask == 0) {
24,864✔
1605
                *ret = NULL;
10,848✔
1606
                return 0;
10,848✔
1607
        }
1608

1609
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
210,240✔
1610
                const char *k;
196,224✔
1611
                size_t l;
196,224✔
1612

1613
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
196,224✔
1614
                        continue;
107,131✔
1615

1616
                k = cgroup_controller_to_string(c);
89,093✔
1617
                l = strlen(k);
89,093✔
1618

1619
                if (!GREEDY_REALLOC(s, n + space + l + 1))
89,093✔
1620
                        return -ENOMEM;
1621

1622
                if (space)
89,093✔
1623
                        s[n] = ' ';
75,077✔
1624
                memcpy(s + n + space, k, l);
89,093✔
1625
                n += space + l;
89,093✔
1626

1627
                space = true;
89,093✔
1628
        }
1629

1630
        assert(s);
14,016✔
1631

1632
        s[n] = 0;
14,016✔
1633
        *ret = TAKE_PTR(s);
14,016✔
1634

1635
        return 0;
14,016✔
1636
}
1637

1638
int cg_mask_from_string(const char *s, CGroupMask *ret) {
13,597✔
1639
        CGroupMask m = 0;
13,597✔
1640

1641
        assert(ret);
13,597✔
1642
        assert(s);
13,597✔
1643

1644
        for (;;) {
101,526✔
1645
                _cleanup_free_ char *n = NULL;
87,929✔
1646
                CGroupController v;
101,526✔
1647
                int r;
101,526✔
1648

1649
                r = extract_first_word(&s, &n, NULL, 0);
101,526✔
1650
                if (r < 0)
101,526✔
1651
                        return r;
×
1652
                if (r == 0)
101,526✔
1653
                        break;
1654

1655
                v = cgroup_controller_from_string(n);
87,929✔
1656
                if (v < 0)
87,929✔
1657
                        continue;
930✔
1658

1659
                m |= CGROUP_CONTROLLER_TO_MASK(v);
86,999✔
1660
        }
1661

1662
        *ret = m;
13,597✔
1663
        return 0;
13,597✔
1664
}
1665

1666
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
579✔
1667
        CGroupMask mask;
579✔
1668
        int r;
579✔
1669

1670
        assert(ret);
579✔
1671

1672
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1673
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1674
         * pseudo-controllers. */
1675

1676
        /* We can read the supported and accessible controllers from the top-level cgroup attribute */
1677
        _cleanup_free_ char *controllers = NULL, *path = NULL;
579✔
1678
        r = cg_get_path(root, "cgroup.controllers", &path);
579✔
1679
        if (r < 0)
579✔
1680
                return r;
1681

1682
        r = read_one_line_file(path, &controllers);
579✔
1683
        if (r < 0)
579✔
1684
                return r;
1685

1686
        r = cg_mask_from_string(controllers, &mask);
579✔
1687
        if (r < 0)
579✔
1688
                return r;
1689

1690
        /* Mask controllers that are not supported in cgroup v2. */
1691
        mask &= CGROUP_MASK_V2;
579✔
1692

1693
        *ret = mask;
579✔
1694
        return 0;
579✔
1695
}
1696

1697
int cg_mask_supported(CGroupMask *ret) {
274✔
1698
        _cleanup_free_ char *root = NULL;
274✔
1699
        int r;
274✔
1700

1701
        r = cg_get_root_path(&root);
274✔
1702
        if (r < 0)
274✔
1703
                return r;
1704

1705
        return cg_mask_supported_subtree(root, ret);
274✔
1706
}
1707

1708
int cg_is_delegated(const char *path) {
18✔
1709
        int r;
18✔
1710

1711
        assert(path);
18✔
1712

1713
        r = cg_get_xattr_bool(path, "trusted.delegate");
18✔
1714
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
18✔
1715
                return r;
1716

1717
        /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the assumption
1718
         * that whoever is trusted enough to own the cgroup, is also trusted enough to decide if it is
1719
         * delegated or not this should be safe. */
1720
        r = cg_get_xattr_bool(path, "user.delegate");
6✔
1721
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
6✔
1722
}
1723

1724
int cg_is_delegated_fd(int fd) {
151✔
1725
        int r;
151✔
1726

1727
        assert(fd >= 0);
151✔
1728

1729
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
151✔
1730
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
151✔
1731
                return r;
1732

1733
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
144✔
1734
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
144✔
1735
}
1736

1737
int cg_has_coredump_receive(const char *path) {
2✔
1738
        int r;
2✔
1739

1740
        assert(path);
2✔
1741

1742
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
1743
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
1744
                return false;
×
1745

1746
        return r;
1747
}
1748

1749
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1750
        [CGROUP_IO_RBPS_MAX]  = CGROUP_LIMIT_MAX,
1751
        [CGROUP_IO_WBPS_MAX]  = CGROUP_LIMIT_MAX,
1752
        [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX,
1753
        [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX,
1754
};
1755

1756
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1757
        [CGROUP_IO_RBPS_MAX]  = "IOReadBandwidthMax",
1758
        [CGROUP_IO_WBPS_MAX]  = "IOWriteBandwidthMax",
1759
        [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax",
1760
        [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax",
1761
};
1762

1763
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
31,660✔
1764

1765
void cgroup_io_limits_list(void) {
20✔
1766
        DUMP_STRING_TABLE(cgroup_io_limit_type, CGroupIOLimitType, _CGROUP_IO_LIMIT_TYPE_MAX);
100✔
1767
}
20✔
1768

1769
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
1770
        [CGROUP_CONTROLLER_CPU]                             = "cpu",
1771
        [CGROUP_CONTROLLER_CPUACCT]                         = "cpuacct",
1772
        [CGROUP_CONTROLLER_CPUSET]                          = "cpuset",
1773
        [CGROUP_CONTROLLER_IO]                              = "io",
1774
        [CGROUP_CONTROLLER_BLKIO]                           = "blkio",
1775
        [CGROUP_CONTROLLER_MEMORY]                          = "memory",
1776
        [CGROUP_CONTROLLER_DEVICES]                         = "devices",
1777
        [CGROUP_CONTROLLER_PIDS]                            = "pids",
1778
        [CGROUP_CONTROLLER_BPF_FIREWALL]                    = "bpf-firewall",
1779
        [CGROUP_CONTROLLER_BPF_DEVICES]                     = "bpf-devices",
1780
        [CGROUP_CONTROLLER_BPF_FOREIGN]                     = "bpf-foreign",
1781
        [CGROUP_CONTROLLER_BPF_SOCKET_BIND]                 = "bpf-socket-bind",
1782
        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
1783
        [CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE]      = "bpf-bind-network-interface",
1784
};
1785

1786
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
813,151✔
1787

1788
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
1789
        [MANAGED_OOM_AUTO] = "auto",
1790
        [MANAGED_OOM_KILL] = "kill",
1791
};
1792

1793
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
53,249✔
1794

1795
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
1796
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
1797
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
1798
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
1799
};
1800

1801
DEFINE_STRING_TABLE_LOOKUP(managed_oom_preference, ManagedOOMPreference);
26,581✔
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