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

systemd / systemd / 28411991350

29 Jun 2026 02:22PM UTC coverage: 72.809% (+0.1%) from 72.714%
28411991350

push

github

yuwata
locale-util: drop libintl dependency

Both glibc and musl provides dgettext(). Hence it is not necessary to
use libintl.so provided by gettext.

This partially reverts 590e22643,
fully reverts commit e6e65dc26 and
bd19ffd9c.

Then, introduce minimal libintl.h for musl build, to avoid using
libintl.h by gettext, which is typically installed on musl-based
build systems.

341580 of 469146 relevant lines covered (72.81%)

1326844.04 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) {
51✔
39
        struct statfs fs;
51✔
40

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

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

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

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

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

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

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

112
        assert(ret);
27,238✔
113

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

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

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

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

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

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

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

146
                        return errno_or_else(EIO);
×
147
                }
148

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

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

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

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

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

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

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

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

196
        if (supported >= 0)
634✔
197
                return supported;
606✔
198

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

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

214
        assert(ret);
27,330✔
215

216
        /* This is not recursive! */
217

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

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

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

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

234
        FOREACH_DIRENT_ALL(de, d, return -errno) {
546,932✔
235
                if (de->d_type != DT_DIR)
535,040✔
236
                        continue;
509,995✔
237

238
                if (dot_or_dot_dot(de->d_name))
25,045✔
239
                        continue;
23,784✔
240

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

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

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

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

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

283
                done = true;
26,759✔
284

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

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

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

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

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

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

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

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

330
                                if (ret == 0) {
213✔
331
                                        if (log_kill)
126✔
332
                                                ret = ret_log_kill;
333
                                        else
334
                                                ret = 1;
90✔
335
                                }
336
                        }
337

338
                        done = false;
213✔
339

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

350
        return ret;
351
}
352

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

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

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

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

376
        r = cg_enumerate_subgroups(path, &d);
26,633✔
377
        if (r < 0) {
26,633✔
378
                if (r != -ENOENT)
15,612✔
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,543✔
385
                _cleanup_free_ char *fn = NULL, *p = NULL;
11,282✔
386

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

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

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

406
        return ret;
11,021✔
407
}
408

409
int cg_kill_kernel_sigkill(const char *path, uint64_t *ret_n_pids_killed) {
634✔
410
        _cleanup_free_ char *killfile = NULL;
634✔
411
        uint64_t n_pids = UINT64_MAX;
634✔
412
        int r;
634✔
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);
634✔
418

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

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

426
        if (ret_n_pids_killed) {
634✔
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. */
430
                r = cg_get_attribute_as_uint64(path, "pids.current", &n_pids);
×
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);
634✔
436
        if (r < 0)
634✔
437
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
438

439
        if (ret_n_pids_killed)
634✔
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) {
605,193✔
446
        char *t;
605,193✔
447

448
        assert(ret);
605,193✔
449

450
        if (isempty(path))
660,929✔
451
                path = TAKE_PTR(suffix);
452

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

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

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

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

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

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

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

480
        assert(path);
32,064✔
481
        assert(name);
32,064✔
482

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

487
        return lgetxattr_malloc(fs, name, ret, ret_size);
32,064✔
488
}
489

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

573
        assert(ret_path);
36,628✔
574

575
        if (!pidref_is_set(pidref))
36,628✔
576
                return -ESRCH;
577
        if (pidref_is_remote(pidref))
73,256✔
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,628✔
585
        if (r < 0)
36,628✔
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,512✔
590
        if (r < 0)
35,512✔
591
                return r;
592

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

597
int cg_is_empty(const char *path) {
5,153✔
598
        _cleanup_free_ char *t = NULL;
5,153✔
599
        int r;
5,153✔
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,153✔
605

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

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

616
        return streq(t, "0");
823✔
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) {
50,164✔
682
        char *p, *e;
50,164✔
683
        int r;
50,164✔
684

685
        assert(ret_path);
50,164✔
686

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

691
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
50,164✔
692
        if (e)
50,164✔
693
                *e = 0;
50,120✔
694

695
        *ret_path = p;
50,164✔
696
        return 0;
50,164✔
697
}
698

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

702
        assert(cgroup);
17,024✔
703
        assert(ret_shifted);
17,024✔
704

705
        _cleanup_free_ char *rt = NULL;
17,024✔
706
        if (!root) {
17,024✔
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,782✔
711
                if (r < 0)
3,782✔
712
                        return r;
713

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

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

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

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

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

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

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

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

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

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

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

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

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

761
        return 0;
762
}
763

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

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

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

774
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
78,766✔
775
}
776

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

780
        /* Skips over all slice assignments */
781

782
        for (;;) {
169,920✔
783
                size_t n;
112,564✔
784

785
                p += strspn(p, "/");
112,564✔
786

787
                n = strcspn(p, "/");
112,564✔
788
                if (!valid_slice_name(p, n))
112,564✔
789
                        return p;
55,208✔
790

791
                p += n;
57,356✔
792
        }
793
}
794

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

798
        assert(path);
22,648✔
799

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

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

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

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

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

824
                path_simplify(subgroup);
835✔
825

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

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

832
        return 0;
833
}
834

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

839
        assert(path);
13,080✔
840
        assert(ret);
13,080✔
841

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

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

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

852
        *ret = TAKE_PTR(path_copy);
13,077✔
853

854
        return 0;
13,077✔
855
}
856

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

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

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

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

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

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

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

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

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

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

897
        if (isempty(p))
19,017✔
898
                return NULL;
899

900
        p += strspn(p, "/");
19,013✔
901

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

906
        const char *s = startswith(p, "session-");
18,765✔
907
        if (!s)
18,765✔
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),
35✔
913
             *e = endswith(f, ".scope");
35✔
914
        if (!e)
35✔
915
                return NULL;
916
        *e = '\0';
35✔
917

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

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

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

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

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

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

934
        n = strcspn(p, "/");
19,476✔
935
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
19,476✔
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);
19,230✔
941
        if (!unit_name)
19,230✔
942
                return NULL;
943

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

947
        if (type != UNIT_NAME_INSTANCE)
19,230✔
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) &&
840✔
954
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
196✔
955
                return NULL;
956

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

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

963
        assert(path);
19,480✔
964

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

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

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

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

980
        assert(path);
9,768✔
981

982
        t = skip_user_prefix(path);
9,768✔
983
        if (!t)
9,768✔
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);
257✔
989
}
990

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

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

999
        return cg_path_get_user_unit_full(cgroup, ret_unit, ret_subgroup);
57✔
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) {
42✔
1027
        _cleanup_free_ char *u = NULL;
42✔
1028
        const char *sl;
42✔
1029
        int r;
42✔
1030

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

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

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

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

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

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

1055
        assert(path);
11,639✔
1056

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

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

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

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

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

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

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

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

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

1092
        if (!pidref_is_set(pidref))
500✔
1093
                return -ESRCH;
500✔
1094
        if (pidref_is_remote(pidref))
1,000✔
1095
                return -EREMOTE;
1096

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

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

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

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

1116
        assert(path);
10,489✔
1117

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

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

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

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

1134
        return 0;
1135
}
1136

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

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

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

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

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

1156
        uid_t uid;
54✔
1157
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
54✔
1158
        if (r < 0)
54✔
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,496✔
1172
        const char *e = NULL;
20,496✔
1173

1174
        assert(p);
20,496✔
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 (;;) {
63,316✔
1180
                const char *s;
41,906✔
1181
                int n;
41,906✔
1182

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

1189
                e = s;
21,410✔
1190
        }
1191

1192
        if (e)
20,496✔
1193
                return cg_path_decode_unit(e, ret_slice);
20,163✔
1194

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

1198
        return 0;
1199
}
1200

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

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

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

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

1216
        t = skip_user_prefix(p);
9,712✔
1217
        if (!t)
9,712✔
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);
241✔
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,701✔
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,701✔
1244
                return true;
1245

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

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

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

1255
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
610,305✔
1256
                const char *q;
569,618✔
1257

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

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

1266
        return false;
1267
}
1268

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

1272
        assert(ret);
40,054✔
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)) {
40,054✔
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);
40,047✔
1290
                if (!n)
40,047✔
1291
                        return -ENOMEM;
1292
        }
1293

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

1298
char* cg_unescape(const char *p) {
134,817✔
1299
        assert(p);
134,817✔
1300

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

1304
        if (p[0] == '_')
134,817✔
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,814✔
1311
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
17,814✔
1312
        const char *dash;
17,814✔
1313
        int r;
17,814✔
1314

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

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

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

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

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

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

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

1337
        while (dash) {
18,997✔
1338
                _cleanup_free_ char *escaped = NULL;
1,207✔
1339
                char n[dash - p + sizeof(".slice")];
1,207✔
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,207✔
1351
                        return -EINVAL;
1352

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

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

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

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

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

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

1374
        *ret = TAKE_PTR(s);
17,790✔
1375
        return 0;
17,790✔
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,519✔
1403
        _cleanup_free_ char *p = NULL;
122,519✔
1404
        int r;
122,519✔
1405

1406
        assert(attribute);
122,519✔
1407

1408
        r = cg_get_path(path, attribute, &p);
122,519✔
1409
        if (r < 0)
122,519✔
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,519✔
1418
}
1419

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

1424
        assert(attribute);
117,578✔
1425

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

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

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

1438
        assert(ret);
98,691✔
1439

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

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

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

1455
        *ret = v;
71,250✔
1456
        return 0;
71,250✔
1457
}
1458

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

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

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,234✔
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,234✔
1501
        size_t n;
59,234✔
1502
        int r;
59,234✔
1503

1504
        assert(path);
59,234✔
1505
        assert(attribute);
59,234✔
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,234✔
1514
        if (r < 0)
59,234✔
1515
                return r;
1516

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

1521
        n = strv_length(keys);
54,832✔
1522
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
54,832✔
1523
                return 0;
1524
        assert(strv_is_uniq(keys));
54,832✔
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,832✔
1528
        size_t n_done = 0;
54,832✔
1529

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

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

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

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

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

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

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

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

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

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

1573
fail:
2✔
1574
        free_many_charp(v, n);
59,236✔
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,961✔
1579
        _cleanup_free_ char *val = NULL;
36,961✔
1580
        int r;
36,961✔
1581

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

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

1589
        r = safe_atou64(val, ret);
36,916✔
1590
        if (r < 0)
36,916✔
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,879✔
1597
        _cleanup_free_ char *s = NULL;
24,879✔
1598
        bool space = false;
24,879✔
1599
        CGroupController c;
24,879✔
1600
        size_t n = 0;
24,879✔
1601

1602
        assert(ret);
24,879✔
1603

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

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

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

1616
                k = cgroup_controller_to_string(c);
88,878✔
1617
                l = strlen(k);
88,878✔
1618

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

1622
                if (space)
88,878✔
1623
                        s[n] = ' ';
74,856✔
1624
                memcpy(s + n + space, k, l);
88,878✔
1625
                n += space + l;
88,878✔
1626

1627
                space = true;
88,878✔
1628
        }
1629

1630
        assert(s);
14,022✔
1631

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

1635
        return 0;
14,022✔
1636
}
1637

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

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

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

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

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

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

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

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

1670
        assert(ret);
581✔
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;
581✔
1678
        r = cg_get_path(root, "cgroup.controllers", &path);
581✔
1679
        if (r < 0)
581✔
1680
                return r;
1681

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

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

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

1693
        *ret = mask;
581✔
1694
        return 0;
581✔
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) {
158✔
1725
        int r;
158✔
1726

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

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

1733
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
151✔
1734
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
151✔
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,662✔
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);
814,191✔
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,344✔
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,630✔
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