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

systemd / systemd / 20012523220

07 Dec 2025 11:33PM UTC coverage: 72.656% (+0.1%) from 72.546%
20012523220

push

github

yuwata
import: include unistd.h for pipe2

This is needed for e.g. pipe2 and unlinkat and a build failure
is reproducible when libarchive support is disabled.

309255 of 425645 relevant lines covered (72.66%)

1123501.7 hits per line

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

90.85
/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
/* The structure to pass to name_to_handle_at() on cgroupfs2 */
39
typedef union {
40
        struct file_handle file_handle;
41
        uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
42
} cg_file_handle;
43

44
#define CG_FILE_HANDLE_INIT                                     \
45
        (cg_file_handle) {                                      \
46
                .file_handle.handle_bytes = sizeof(uint64_t),   \
47
                .file_handle.handle_type = FILEID_KERNFS,       \
48
        }
49

50
/* The .f_handle field is not aligned to 64bit on some archs, hence read it via an unaligned accessor */
51
#define CG_FILE_HANDLE_CGROUPID(fh) unaligned_read_ne64(fh.file_handle.f_handle)
52

53
int cg_is_available(void) {
24✔
54
        struct statfs fs;
24✔
55

56
        if (statfs("/sys/fs/cgroup/", &fs) < 0) {
24✔
57
                if (errno == ENOENT) /* sysfs not mounted? */
×
58
                        return false;
24✔
59

60
                return log_debug_errno(errno, "Failed to statfs /sys/fs/cgroup/: %m");
×
61
        }
62

63
        return is_fs_type(&fs, CGROUP2_SUPER_MAGIC);
24✔
64
}
65

66
int cg_path_open(const char *path) {
839✔
67
        _cleanup_free_ char *fs = NULL;
839✔
68
        int r;
839✔
69

70
        r = cg_get_path(path, /* suffix= */ NULL, &fs);
839✔
71
        if (r < 0)
839✔
72
                return r;
73

74
        return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC));
839✔
75
}
76

77
int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) {
12✔
78
        _cleanup_close_ int fsfd = -EBADF;
12✔
79

80
        if (cgroupfs_fd < 0) {
12✔
81
                fsfd = open("/sys/fs/cgroup", O_CLOEXEC|O_DIRECTORY);
11✔
82
                if (fsfd < 0)
11✔
83
                        return -errno;
×
84

85
                cgroupfs_fd = fsfd;
86
        }
87

88
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
12✔
89
        unaligned_write_ne64(fh.file_handle.f_handle, id);
12✔
90

91
        return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
20✔
92
}
93

94
int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret) {
×
95
        _cleanup_close_ int cgfd = -EBADF;
×
96
        int r;
×
97

98
        cgfd = cg_cgroupid_open(cgroupfs_fd, id);
×
99
        if (cgfd < 0)
×
100
                return cgfd;
101

102
        _cleanup_free_ char *path = NULL;
×
103
        r = fd_get_path(cgfd, &path);
×
104
        if (r < 0)
×
105
                return r;
106

107
        if (!path_startswith(path, "/sys/fs/cgroup/"))
×
108
                return -EXDEV; /* recognizable error */
109

110
        if (ret)
×
111
                *ret = TAKE_PTR(path);
×
112
        return 0;
113
}
114

115
int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret) {
5,466✔
116
        cg_file_handle fh = CG_FILE_HANDLE_INIT;
5,466✔
117
        int mnt_id;
5,466✔
118

119
        assert(dfd >= 0 || (dfd == AT_FDCWD && path_is_absolute(path)));
10,886✔
120
        assert(ret);
5,466✔
121

122
        /* This is cgroupfs so we know the size of the handle, thus no need to loop around like
123
         * name_to_handle_at_loop() does in mountpoint-util.c */
124
        if (name_to_handle_at(dfd, strempty(path), &fh.file_handle, &mnt_id, isempty(path) ? AT_EMPTY_PATH : 0) < 0) {
10,932✔
125
                assert(errno != EOVERFLOW);
×
126
                return -errno;
×
127
        }
128

129
        *ret = CG_FILE_HANDLE_CGROUPID(fh);
5,466✔
130
        return 0;
5,466✔
131
}
132

133
int cg_enumerate_processes(const char *path, FILE **ret) {
16,720✔
134
        _cleanup_free_ char *fs = NULL;
16,720✔
135
        FILE *f;
16,720✔
136
        int r;
16,720✔
137

138
        assert(ret);
16,720✔
139

140
        r = cg_get_path(path, "cgroup.procs", &fs);
16,720✔
141
        if (r < 0)
16,720✔
142
                return r;
143

144
        f = fopen(fs, "re");
16,720✔
145
        if (!f)
16,720✔
146
                return -errno;
10,395✔
147

148
        *ret = f;
6,325✔
149
        return 0;
6,325✔
150
}
151

152
int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags) {
10,468✔
153
        unsigned long ul;
10,468✔
154

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

157
        assert(f);
10,468✔
158
        assert(ret);
10,468✔
159

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

163
        for (;;) {
10,468✔
164
                errno = 0;
10,468✔
165
                if (fscanf(f, "%lu", &ul) != 1) {
10,468✔
166

167
                        if (feof(f)) {
6,540✔
168
                                *ret = 0;
6,540✔
169
                                return 0;
6,540✔
170
                        }
171

172
                        return errno_or_else(EIO);
×
173
                }
174

175
                if (ul > PID_T_MAX)
3,928✔
176
                        return -EIO;
177

178
                /* In some circumstances (e.g. WSL), cgroups might contain unmappable PIDs from other
179
                 * contexts. These show up as zeros, and depending on the caller, can either be plain
180
                 * skipped over, or returned as-is. */
181
                if (ul == 0 && !FLAGS_SET(flags, CGROUP_DONT_SKIP_UNMAPPED))
3,928✔
182
                        continue;
×
183

184
                *ret = (pid_t) ul;
3,928✔
185
                return 1;
3,928✔
186
        }
187
}
188

189
int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) {
7,530✔
190
        int r;
7,530✔
191

192
        assert(f);
7,530✔
193
        assert(ret);
7,530✔
194

195
        for (;;) {
×
196
                pid_t pid;
7,530✔
197

198
                r = cg_read_pid(f, &pid, flags);
7,530✔
199
                if (r < 0)
7,530✔
200
                        return log_debug_errno(r, "Failed to read pid from cgroup item: %m");
×
201
                if (r == 0) {
7,530✔
202
                        *ret = PIDREF_NULL;
5,947✔
203
                        return 0;
5,947✔
204
                }
205

206
                if (pid == 0)
1,583✔
207
                        return -EREMOTE;
208

209
                r = pidref_set_pid(ret, pid);
1,583✔
210
                if (r >= 0)
1,583✔
211
                        return 1;
212
                if (r != -ESRCH)
×
213
                        return r;
214

215
                /* ESRCH → gone by now? just skip over it, read the next */
216
        }
217
}
218

219
bool cg_kill_supported(void) {
×
220
        static thread_local int supported = -1;
×
221

222
        if (supported >= 0)
×
223
                return supported;
×
224

225
        if (cg_is_available() <= 0)
×
226
                return (supported = false);
×
227

228
        if (access("/sys/fs/cgroup/init.scope/cgroup.kill", F_OK) >= 0)
×
229
                return (supported = true);
×
230
        if (errno != ENOENT)
×
231
                log_debug_errno(errno, "Failed to check whether cgroup.kill is available, assuming not: %m");
×
232
        return (supported = false);
×
233
}
234

235
int cg_enumerate_subgroups(const char *path, DIR **ret) {
16,287✔
236
        _cleanup_free_ char *fs = NULL;
16,287✔
237
        DIR *d;
16,287✔
238
        int r;
16,287✔
239

240
        assert(ret);
16,287✔
241

242
        /* This is not recursive! */
243

244
        r = cg_get_path(path, /* suffix = */ NULL, &fs);
16,287✔
245
        if (r < 0)
16,287✔
246
                return r;
247

248
        d = opendir(fs);
16,287✔
249
        if (!d)
16,287✔
250
                return -errno;
10,395✔
251

252
        *ret = d;
5,892✔
253
        return 0;
5,892✔
254
}
255

256
int cg_read_subgroup(DIR *d, char **ret) {
7,279✔
257
        assert(d);
7,279✔
258
        assert(ret);
7,279✔
259

260
        FOREACH_DIRENT_ALL(de, d, return -errno) {
283,266✔
261
                if (de->d_type != DT_DIR)
277,159✔
262
                        continue;
263,773✔
263

264
                if (dot_or_dot_dot(de->d_name))
13,386✔
265
                        continue;
12,214✔
266

267
                return strdup_to_full(ret, de->d_name);
1,172✔
268
        }
269

270
        *ret = NULL;
6,107✔
271
        return 0;
6,107✔
272
}
273

274
int cg_kill(
16,218✔
275
                const char *path,
276
                int sig,
277
                CGroupFlags flags,
278
                Set *killed_pids,
279
                cg_kill_log_func_t log_kill,
280
                void *userdata) {
281

282
        _cleanup_set_free_ Set *allocated_set = NULL;
16,218✔
283
        int r, ret = 0;
16,218✔
284

285
        assert(path);
16,218✔
286
        assert(sig >= 0);
16,218✔
287

288
         /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence
289
          * don't send SIGCONT on SIGKILL. */
290
        if (IN_SET(sig, SIGCONT, SIGKILL))
16,218✔
291
                flags &= ~CGROUP_SIGCONT;
2,457✔
292

293
        /* This goes through the tasks list and kills them all. This is repeated until no further processes
294
         * are added to the tasks list, to properly handle forking processes.
295
         *
296
         * When sending SIGKILL, prefer cg_kill_kernel_sigkill(), which is fully atomic. */
297

298
        if (!killed_pids) {
16,218✔
299
                killed_pids = allocated_set = set_new(NULL);
675✔
300
                if (!killed_pids)
675✔
301
                        return -ENOMEM;
302
        }
303

304
        bool done;
16,299✔
305
        do {
16,299✔
306
                _cleanup_fclose_ FILE *f = NULL;
10,395✔
307
                int ret_log_kill;
16,299✔
308

309
                done = true;
16,299✔
310

311
                r = cg_enumerate_processes(path, &f);
16,299✔
312
                if (r == -ENOENT)
16,299✔
313
                        break;
314
                if (r < 0)
5,904✔
315
                        return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m"));
×
316

317
                for (;;) {
7,456✔
318
                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
7,456✔
319

320
                        r = cg_read_pidref(f, &pidref, flags);
7,456✔
321
                        if (r == -ENODEV) {
7,456✔
322
                                /* reading from cgroup.pids will result in ENODEV if the cgroup is
323
                                 * concurrently removed. Just leave in that case, because a removed cgroup
324
                                 * contains no processes anymore. */
325
                                done = true;
326
                                break;
327
                        }
328
                        if (r < 0)
7,456✔
329
                                return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path));
×
330
                        if (r == 0)
7,456✔
331
                                break;
332

333
                        if ((flags & CGROUP_IGNORE_SELF) && pidref_is_self(&pidref))
1,552✔
334
                                continue;
673✔
335

336
                        if (set_contains(killed_pids, PID_TO_PTR(pidref.pid)))
879✔
337
                                continue;
625✔
338

339
                        /* Ignore kernel threads to mimic the behavior of cgroup.kill. */
340
                        if (pidref_is_kernel_thread(&pidref) > 0) {
254✔
341
                                log_debug("Ignoring kernel thread with pid " PID_FMT " in cgroup '%s'", pidref.pid, path);
×
342
                                continue;
×
343
                        }
344

345
                        if (log_kill)
254✔
346
                                ret_log_kill = log_kill(&pidref, sig, userdata);
67✔
347

348
                        /* If we haven't killed this process yet, kill it */
349
                        r = pidref_kill(&pidref, sig);
254✔
350
                        if (r < 0 && r != -ESRCH)
254✔
351
                                RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path));
×
352
                        if (r >= 0) {
254✔
353
                                if (flags & CGROUP_SIGCONT)
254✔
354
                                        (void) pidref_kill(&pidref, SIGCONT);
138✔
355

356
                                if (ret == 0) {
254✔
357
                                        if (log_kill)
123✔
358
                                                ret = ret_log_kill;
359
                                        else
360
                                                ret = 1;
56✔
361
                                }
362
                        }
363

364
                        done = false;
254✔
365

366
                        r = set_put(killed_pids, PID_TO_PTR(pidref.pid));
254✔
367
                        if (r < 0)
254✔
368
                                return RET_GATHER(ret, r);
×
369
                }
370

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

374
        } while (!done);
5,904✔
375

376
        return ret;
377
}
378

379
int cg_kill_recursive(
15,541✔
380
                const char *path,
381
                int sig,
382
                CGroupFlags flags,
383
                Set *killed_pids,
384
                cg_kill_log_func_t log_kill,
385
                void *userdata) {
386

387
        _cleanup_set_free_ Set *allocated_set = NULL;
×
388
        _cleanup_closedir_ DIR *d = NULL;
15,541✔
389
        int r, ret;
15,541✔
390

391
        assert(path);
15,541✔
392
        assert(sig >= 0);
15,541✔
393

394
        if (!killed_pids) {
15,541✔
395
                killed_pids = allocated_set = set_new(NULL);
15,030✔
396
                if (!killed_pids)
15,030✔
397
                        return -ENOMEM;
398
        }
399

400
        ret = cg_kill(path, sig, flags, killed_pids, log_kill, userdata);
15,541✔
401

402
        r = cg_enumerate_subgroups(path, &d);
15,541✔
403
        if (r < 0) {
15,541✔
404
                if (r != -ENOENT)
10,395✔
405
                        RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path));
×
406

407
                return ret;
10,395✔
408
        }
409

410
        for (;;) {
5,414✔
411
                _cleanup_free_ char *fn = NULL, *p = NULL;
5,280✔
412

413
                r = cg_read_subgroup(d, &fn);
5,280✔
414
                if (r < 0) {
5,280✔
415
                        RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path));
×
416
                        break;
417
                }
418
                if (r == 0)
5,280✔
419
                        break;
420

421
                p = path_join(empty_to_root(path), fn);
134✔
422
                if (!p)
134✔
423
                        return -ENOMEM;
×
424

425
                r = cg_kill_recursive(p, sig, flags, killed_pids, log_kill, userdata);
134✔
426
                if (r < 0)
134✔
427
                        log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p);
×
428
                if (r != 0 && ret >= 0)
134✔
429
                        ret = r;
19✔
430
        }
431

432
        return ret;
5,146✔
433
}
434

435
int cg_kill_kernel_sigkill(const char *path) {
×
436
        _cleanup_free_ char *killfile = NULL;
×
437
        int r;
×
438

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

442
        assert(path);
×
443

444
        if (!cg_kill_supported())
×
445
                return -EOPNOTSUPP;
446

447
        r = cg_get_path(path, "cgroup.kill", &killfile);
×
448
        if (r < 0)
×
449
                return r;
450

451
        r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER);
×
452
        if (r < 0)
×
453
                return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path);
×
454

455
        return 0;
456
}
457

458
int cg_get_path(const char *path, const char *suffix, char **ret) {
275,952✔
459
        char *t;
275,952✔
460

461
        assert(ret);
275,952✔
462

463
        if (isempty(path))
300,848✔
464
                path = TAKE_PTR(suffix);
465

466
        t = path_join("/sys/fs/cgroup", path, suffix);
275,952✔
467
        if (!t)
275,952✔
468
                return -ENOMEM;
469

470
        *ret = path_simplify(t);
275,952✔
471
        return 0;
275,952✔
472
}
473

474
int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags) {
7,226✔
475
        _cleanup_free_ char *fs = NULL;
7,226✔
476
        int r;
7,226✔
477

478
        assert(path);
7,226✔
479
        assert(name);
7,226✔
480
        assert(value || size <= 0);
7,226✔
481

482
        r = cg_get_path(path, /* suffix = */ NULL, &fs);
7,226✔
483
        if (r < 0)
7,226✔
484
                return r;
485

486
        return RET_NERRNO(setxattr(fs, name, value, size, flags));
7,226✔
487
}
488

489
int cg_get_xattr(const char *path, const char *name, char **ret, size_t *ret_size) {
17,523✔
490
        _cleanup_free_ char *fs = NULL;
17,523✔
491
        int r;
17,523✔
492

493
        assert(path);
17,523✔
494
        assert(name);
17,523✔
495

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

500
        return lgetxattr_malloc(fs, name, ret, ret_size);
17,523✔
501
}
502

503
int cg_get_xattr_bool(const char *path, const char *name) {
167✔
504
        _cleanup_free_ char *fs = NULL;
167✔
505
        int r;
167✔
506

507
        assert(path);
167✔
508
        assert(name);
167✔
509

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

514
        return getxattr_at_bool(AT_FDCWD, fs, name, /* at_flags= */ 0);
167✔
515
}
516

517
int cg_remove_xattr(const char *path, const char *name) {
35,474✔
518
        _cleanup_free_ char *fs = NULL;
35,474✔
519
        int r;
35,474✔
520

521
        assert(path);
35,474✔
522
        assert(name);
35,474✔
523

524
        r = cg_get_path(path, /* suffix = */ NULL, &fs);
35,474✔
525
        if (r < 0)
35,474✔
526
                return r;
527

528
        return RET_NERRNO(removexattr(fs, name));
70,948✔
529
}
530

531
int cg_pid_get_path(pid_t pid, char **ret_path) {
53,951✔
532
        _cleanup_fclose_ FILE *f = NULL;
53,951✔
533
        const char *fs;
53,951✔
534
        int r;
53,951✔
535

536
        assert(pid >= 0);
53,951✔
537
        assert(ret_path);
53,951✔
538

539
        fs = procfs_file_alloca(pid, "cgroup");
60,243✔
540
        r = fopen_unlocked(fs, "re", &f);
53,951✔
541
        if (r == -ENOENT)
53,951✔
542
                return -ESRCH;
543
        if (r < 0)
49,439✔
544
                return r;
545

546
        for (;;) {
49,439✔
547
                _cleanup_free_ char *line = NULL;
49,439✔
548
                char *e;
49,439✔
549

550
                r = read_line(f, LONG_LINE_MAX, &line);
49,439✔
551
                if (r < 0)
49,439✔
552
                        return r;
553
                if (r == 0)
49,439✔
554
                        return -ENODATA;
555

556
                e = startswith(line, "0:");
49,439✔
557
                if (!e)
49,439✔
558
                        continue;
×
559

560
                e = strchr(e, ':');
49,439✔
561
                if (!e)
49,439✔
562
                        continue;
×
563

564
                _cleanup_free_ char *path = strdup(e + 1);
49,439✔
565
                if (!path)
49,439✔
566
                        return -ENOMEM;
567

568
                /* Refuse cgroup paths from outside our cgroup namespace */
569
                if (startswith(path, "/../"))
49,439✔
570
                        return -EUNATCH;
571

572
                /* Truncate suffix indicating the process is a zombie */
573
                e = endswith(path, " (deleted)");
49,439✔
574
                if (e)
49,439✔
575
                        *e = 0;
167✔
576

577
                *ret_path = TAKE_PTR(path);
49,439✔
578
                return 0;
49,439✔
579
        }
580
}
581

582
int cg_pidref_get_path(const PidRef *pidref, char **ret_path) {
12,181✔
583
        _cleanup_free_ char *path = NULL;
12,181✔
584
        int r;
12,181✔
585

586
        assert(ret_path);
12,181✔
587

588
        if (!pidref_is_set(pidref))
12,181✔
589
                return -ESRCH;
590
        if (pidref_is_remote(pidref))
24,362✔
591
                return -EREMOTE;
592

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

597
        r = cg_pid_get_path(pidref->pid, &path);
12,181✔
598
        if (r < 0)
12,181✔
599
                return r;
600

601
        /* Before we return the path, make sure the procfs entry for this pid still matches the pidref */
602
        r = pidref_verify(pidref);
12,179✔
603
        if (r < 0)
12,179✔
604
                return r;
605

606
        *ret_path = TAKE_PTR(path);
12,179✔
607
        return 0;
12,179✔
608
}
609

610
int cg_is_empty(const char *path) {
2,804✔
611
        _cleanup_free_ char *t = NULL;
2,804✔
612
        int r;
2,804✔
613

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

617
        assert(path);
2,804✔
618

619
        /* The root cgroup is always populated */
620
        if (empty_or_root(path))
2,804✔
621
                return false;
622

623
        r = cg_get_keyed_attribute(path, "cgroup.events", STRV_MAKE("populated"), &t);
2,804✔
624
        if (r == -ENOENT)
2,804✔
625
                return true;
626
        if (r < 0)
301✔
627
                return r;
628

629
        return streq(t, "0");
301✔
630
}
631

632
int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) {
19✔
633
        _cleanup_free_ char *controller = NULL;
19✔
634
        const char *path;
19✔
635
        int r;
19✔
636

637
        assert(spec);
19✔
638

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

642
        if (isempty(spec) || path_is_absolute(spec)) {
19✔
643
                /* Assume this does not contain controller. */
644
                path = spec;
12✔
645
                goto finalize;
12✔
646
        }
647

648
        const char *e = strchr(spec, ':');
7✔
649
        if (!e) {
7✔
650
                /* Controller only. */
651
                if (ret_controller) {
1✔
652
                        controller = strdup(spec);
1✔
653
                        if (!controller)
1✔
654
                                return -ENOMEM;
655
                }
656

657
                path = NULL;
658
        } else {
659
                /* Both controller and path. */
660
                if (ret_controller) {
6✔
661
                        controller = strndup(spec, e - spec);
6✔
662
                        if (!controller)
6✔
663
                                return -ENOMEM;
664
                }
665

666
                path = e + 1;
6✔
667
        }
668

669
finalize:
12✔
670
        path = empty_to_null(path);
18✔
671

672
        if (path) {
15✔
673
                /* Non-empty path must be absolute. */
674
                if (!path_is_absolute(path))
15✔
675
                        return -EINVAL;
676

677
                /* Path must not contain dot-dot. */
678
                if (!path_is_safe(path))
14✔
679
                        return -EINVAL;
680
        }
681

682
        if (ret_path) {
18✔
683
                r = path_simplify_alloc(path, ret_path);
18✔
684
                if (r < 0)
18✔
685
                        return r;
686
        }
687

688
        if (ret_controller)
18✔
689
                *ret_controller = TAKE_PTR(controller);
18✔
690

691
        return 0;
692
}
693

694
int cg_get_root_path(char **ret_path) {
23,447✔
695
        char *p, *e;
23,447✔
696
        int r;
23,447✔
697

698
        assert(ret_path);
23,447✔
699

700
        r = cg_pid_get_path(1, &p);
23,447✔
701
        if (r < 0)
23,447✔
702
                return r;
23,447✔
703

704
        e = endswith(p, "/" SPECIAL_INIT_SCOPE);
23,447✔
705
        if (e)
23,447✔
706
                *e = 0;
23,414✔
707

708
        *ret_path = p;
23,447✔
709
        return 0;
23,447✔
710
}
711

712
int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) {
12,416✔
713
        int r;
12,416✔
714

715
        assert(cgroup);
12,416✔
716
        assert(ret_shifted);
12,416✔
717

718
        _cleanup_free_ char *rt = NULL;
12,416✔
719
        if (!root) {
12,416✔
720
                /* If the root was specified let's use that, otherwise
721
                 * let's determine it from PID 1 */
722

723
                r = cg_get_root_path(&rt);
2,469✔
724
                if (r < 0)
2,469✔
725
                        return r;
726

727
                root = rt;
2,469✔
728
        }
729

730
        *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup;
12,416✔
731
        return 0;
12,416✔
732
}
733

734
int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) {
16,748✔
735
        _cleanup_free_ char *raw = NULL;
16,748✔
736
        const char *c;
16,748✔
737
        int r;
16,748✔
738

739
        assert(pid >= 0);
16,748✔
740
        assert(ret_cgroup);
16,748✔
741

742
        r = cg_pid_get_path(pid, &raw);
16,748✔
743
        if (r < 0)
16,748✔
744
                return r;
745

746
        r = cg_shift_path(raw, root, &c);
12,238✔
747
        if (r < 0)
12,238✔
748
                return r;
749

750
        if (c == raw) {
12,238✔
751
                *ret_cgroup = TAKE_PTR(raw);
12,238✔
752
                return 0;
12,238✔
753
        }
754

755
        return strdup_to(ret_cgroup, c);
×
756
}
757

758
int cg_path_decode_unit(const char *cgroup, char **ret_unit) {
33,919✔
759
        assert(cgroup);
33,919✔
760

761
        size_t n = strcspn(cgroup, "/");
33,919✔
762
        if (n < 3)
33,919✔
763
                return -ENXIO;
764

765
        char *c = strndupa_safe(cgroup, n);
33,907✔
766
        c = cg_unescape(c);
33,907✔
767

768
        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
33,907✔
769
                return -ENXIO;
770

771
        if (ret_unit)
33,898✔
772
                return strdup_to(ret_unit, c);
33,898✔
773

774
        return 0;
775
}
776

777
static bool valid_slice_name(const char *p, size_t n) {
120,998✔
778
        assert(p || n == 0);
120,998✔
779

780
        if (n < STRLEN("x.slice"))
120,998✔
781
                return false;
782

783
        char *c = strndupa_safe(p, n);
120,957✔
784
        if (!endswith(c, ".slice"))
120,957✔
785
                return false;
786

787
        return unit_name_is_valid(cg_unescape(c), UNIT_NAME_PLAIN);
61,392✔
788
}
789

790
static const char* skip_slices(const char *p) {
43,345✔
791
        assert(p);
43,345✔
792

793
        /* Skips over all slice assignments */
794

795
        for (;;) {
132,643✔
796
                size_t n;
87,994✔
797

798
                p += strspn(p, "/");
87,994✔
799

800
                n = strcspn(p, "/");
87,994✔
801
                if (!valid_slice_name(p, n))
87,994✔
802
                        return p;
43,345✔
803

804
                p += n;
44,649✔
805
        }
806
}
807

808
int cg_path_get_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
17,893✔
809
        int r;
17,893✔
810

811
        assert(path);
17,893✔
812

813
        const char *e = skip_slices(path);
17,893✔
814

815
        _cleanup_free_ char *unit = NULL;
17,893✔
816
        r = cg_path_decode_unit(e, &unit);
17,893✔
817
        if (r < 0)
17,893✔
818
                return r;
819

820
        /* We skipped over the slices, don't accept any now */
821
        if (endswith(unit, ".slice"))
17,876✔
822
                return -ENXIO;
823

824
        if (ret_subgroup) {
17,876✔
825
                _cleanup_free_ char *subgroup = NULL;
×
826
                e += strcspn(e, "/");
707✔
827
                e += strspn(e, "/");
707✔
828

829
                if (isempty(e))
707✔
830
                        subgroup = NULL;
831
                else {
832
                        subgroup = strdup(e);
240✔
833
                        if (!subgroup)
240✔
834
                                return -ENOMEM;
×
835
                }
836

837
                path_simplify(subgroup);
707✔
838

839
                *ret_subgroup = TAKE_PTR(subgroup);
707✔
840
        }
841

842
        if (ret_unit)
17,876✔
843
                *ret_unit = TAKE_PTR(unit);
17,876✔
844

845
        return 0;
846
}
847

848
int cg_path_get_unit_path(const char *path, char **ret) {
9,785✔
849
        _cleanup_free_ char *path_copy = NULL;
9,785✔
850
        char *unit_name;
9,785✔
851

852
        assert(path);
9,785✔
853
        assert(ret);
9,785✔
854

855
        path_copy = strdup(path);
9,785✔
856
        if (!path_copy)
9,785✔
857
                return -ENOMEM;
858

859
        unit_name = (char*) skip_slices(path_copy);
9,785✔
860
        unit_name[strcspn(unit_name, "/")] = 0;
9,785✔
861

862
        if (!unit_name_is_valid(cg_unescape(unit_name), UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
9,785✔
863
                return -ENXIO;
864

865
        *ret = TAKE_PTR(path_copy);
9,782✔
866

867
        return 0;
9,782✔
868
}
869

870
int cg_pid_get_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
770✔
871
        int r;
770✔
872

873
        _cleanup_free_ char *cgroup = NULL;
770✔
874
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
770✔
875
        if (r < 0)
770✔
876
                return r;
877

878
        return cg_path_get_unit_full(cgroup, ret_unit, ret_subgroup);
770✔
879
}
880

881
int cg_pidref_get_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
685✔
882
        int r;
685✔
883

884
        if (!pidref_is_set(pidref))
685✔
885
                return -ESRCH;
685✔
886
        if (pidref_is_remote(pidref))
1,370✔
887
                return -EREMOTE;
888

889
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
685✔
890
        r = cg_pid_get_unit_full(pidref->pid, &unit, &subgroup);
685✔
891
        if (r < 0)
685✔
892
                return r;
893

894
        r = pidref_verify(pidref);
685✔
895
        if (r < 0)
685✔
896
                return r;
897

898
        if (ret_unit)
685✔
899
                *ret_unit = TAKE_PTR(unit);
685✔
900
        if (ret_subgroup)
685✔
901
                *ret_subgroup = TAKE_PTR(subgroup);
49✔
902
        return 0;
903
}
904

905
static const char* skip_session(const char *p) {
15,260✔
906
        size_t n;
15,260✔
907

908
        /* Skip session-*.scope, but require it to be there. */
909

910
        if (isempty(p))
15,260✔
911
                return NULL;
912

913
        p += strspn(p, "/");
15,256✔
914

915
        n = strcspn(p, "/");
15,256✔
916
        if (n < STRLEN("session-x.scope"))
15,256✔
917
                return NULL;
918

919
        const char *s = startswith(p, "session-");
15,088✔
920
        if (!s)
15,088✔
921
                return NULL;
922

923
        /* Note that session scopes never need unescaping, since they cannot conflict with the kernel's
924
         * own names, hence we don't need to call cg_unescape() here. */
925
        char *f = strndupa_safe(s, p + n - s),
27✔
926
             *e = endswith(f, ".scope");
27✔
927
        if (!e)
27✔
928
                return NULL;
929
        *e = '\0';
27✔
930

931
        if (!session_id_valid(f))
27✔
932
                return NULL;
933

934
        return skip_leading_slash(p + n);
27✔
935
}
936

937
static const char* skip_user_manager(const char *p) {
15,667✔
938
        size_t n;
15,667✔
939

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

942
        if (isempty(p))
15,667✔
943
                return NULL;
15,667✔
944

945
        p += strspn(p, "/");
15,663✔
946

947
        n = strcspn(p, "/");
15,663✔
948
        if (n < CONST_MIN(STRLEN("user@x.service"), STRLEN("capsule@x.service")))
15,663✔
949
                return NULL;
950

951
        /* Any possible errors from functions called below are converted to NULL return, so our callers won't
952
         * resolve user/capsule name. */
953
        _cleanup_free_ char *unit_name = strndup(p, n);
15,497✔
954
        if (!unit_name)
15,497✔
955
                return NULL;
956

957
        _cleanup_free_ char *i = NULL;
15,497✔
958
        UnitNameFlags type = unit_name_to_instance(unit_name, &i);
15,497✔
959

960
        if (type != UNIT_NAME_INSTANCE)
15,497✔
961
                return NULL;
962

963
        /* Note that user manager services never need unescaping, since they cannot conflict with the
964
         * kernel's own names, hence we don't need to call cg_unescape() here.  Prudently check validity of
965
         * instance names, they should be always valid as we validate them upon unit start. */
966
        if (!(startswith(unit_name, "user@") && parse_uid(i, NULL) >= 0) &&
620✔
967
            !(startswith(unit_name, "capsule@") && capsule_name_is_valid(i) > 0))
114✔
968
                return NULL;
104✔
969

970
        return skip_leading_slash(p + n);
407✔
971
}
972

973
static const char* skip_user_prefix(const char *path) {
15,667✔
974
        const char *e, *t;
15,667✔
975

976
        assert(path);
15,667✔
977

978
        /* Skip slices, if there are any */
979
        e = skip_slices(path);
15,667✔
980

981
        /* Skip the user manager, if it's in the path now... */
982
        t = skip_user_manager(e);
15,667✔
983
        if (t)
15,667✔
984
                return t;
985

986
        /* Alternatively skip the user session if it is in the path... */
987
        return skip_session(e);
15,260✔
988
}
989

990
int cg_path_get_user_unit_full(const char *path, char **ret_unit, char **ret_subgroup) {
7,864✔
991
        const char *t;
7,864✔
992

993
        assert(path);
7,864✔
994

995
        t = skip_user_prefix(path);
7,864✔
996
        if (!t)
7,864✔
997
                return -ENXIO;
998

999
        /* And from here on it looks pretty much the same as for a system unit, hence let's use the same
1000
         * parser. */
1001
        return cg_path_get_unit_full(t, ret_unit, ret_subgroup);
227✔
1002
}
1003

1004
int cg_pid_get_user_unit_full(pid_t pid, char **ret_unit, char **ret_subgroup) {
62✔
1005
        int r;
62✔
1006

1007
        _cleanup_free_ char *cgroup = NULL;
62✔
1008
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
62✔
1009
        if (r < 0)
62✔
1010
                return r;
1011

1012
        return cg_path_get_user_unit_full(cgroup, ret_unit, ret_subgroup);
62✔
1013
}
1014

1015
int cg_pidref_get_user_unit_full(const PidRef *pidref, char **ret_unit, char **ret_subgroup) {
17✔
1016
        int r;
17✔
1017

1018
        if (!pidref_is_set(pidref))
17✔
1019
                return -ESRCH;
17✔
1020
        if (pidref_is_remote(pidref))
34✔
1021
                return -EREMOTE;
1022

1023
        _cleanup_free_ char *unit = NULL, *subgroup = NULL;
17✔
1024
        r = cg_pid_get_user_unit_full(pidref->pid, &unit, &subgroup);
17✔
1025
        if (r < 0)
17✔
1026
                return r;
1027

1028
        r = pidref_verify(pidref);
8✔
1029
        if (r < 0)
8✔
1030
                return r;
1031

1032
        if (ret_unit)
8✔
1033
                *ret_unit = TAKE_PTR(unit);
8✔
1034
        if (ret_subgroup)
8✔
1035
                *ret_subgroup = TAKE_PTR(subgroup);
4✔
1036
        return 0;
1037
}
1038

1039
int cg_path_get_machine_name(const char *path, char **ret_machine) {
43✔
1040
        _cleanup_free_ char *u = NULL;
43✔
1041
        const char *sl;
43✔
1042
        int r;
43✔
1043

1044
        r = cg_path_get_unit(path, &u);
43✔
1045
        if (r < 0)
43✔
1046
                return r;
1047

1048
        sl = strjoina("/run/systemd/machines/unit:", u);
215✔
1049
        return readlink_malloc(sl, ret_machine);
43✔
1050
}
1051

1052
int cg_pid_get_machine_name(pid_t pid, char **ret_machine) {
43✔
1053
        _cleanup_free_ char *cgroup = NULL;
43✔
1054
        int r;
43✔
1055

1056
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
43✔
1057
        if (r < 0)
43✔
1058
                return r;
1059

1060
        return cg_path_get_machine_name(cgroup, ret_machine);
43✔
1061
}
1062

1063
int cg_path_get_session(const char *path, char **ret_session) {
8,982✔
1064
        _cleanup_free_ char *unit = NULL;
8,982✔
1065
        char *start, *end;
8,982✔
1066
        int r;
8,982✔
1067

1068
        assert(path);
8,982✔
1069

1070
        r = cg_path_get_unit(path, &unit);
8,982✔
1071
        if (r < 0)
8,982✔
1072
                return r;
1073

1074
        start = startswith(unit, "session-");
8,981✔
1075
        if (!start)
8,981✔
1076
                return -ENXIO;
1077
        end = endswith(start, ".scope");
426✔
1078
        if (!end)
426✔
1079
                return -ENXIO;
1080

1081
        *end = 0;
426✔
1082
        if (!session_id_valid(start))
426✔
1083
                return -ENXIO;
1084

1085
        if (!ret_session)
425✔
1086
                return 0;
1087

1088
        return strdup_to(ret_session, start);
425✔
1089
}
1090

1091
int cg_pid_get_session(pid_t pid, char **ret_session) {
1,119✔
1092
        _cleanup_free_ char *cgroup = NULL;
1,119✔
1093
        int r;
1,119✔
1094

1095
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1,119✔
1096
        if (r < 0)
1,119✔
1097
                return r;
1098

1099
        return cg_path_get_session(cgroup, ret_session);
1,119✔
1100
}
1101

1102
int cg_pidref_get_session(const PidRef *pidref, char **ret) {
434✔
1103
        int r;
434✔
1104

1105
        if (!pidref_is_set(pidref))
434✔
1106
                return -ESRCH;
434✔
1107
        if (pidref_is_remote(pidref))
868✔
1108
                return -EREMOTE;
1109

1110
        _cleanup_free_ char *session = NULL;
434✔
1111
        r = cg_pid_get_session(pidref->pid, &session);
434✔
1112
        if (r < 0)
434✔
1113
                return r;
1114

1115
        r = pidref_verify(pidref);
379✔
1116
        if (r < 0)
379✔
1117
                return r;
1118

1119
        if (ret)
379✔
1120
                *ret = TAKE_PTR(session);
379✔
1121
        return 0;
1122
}
1123

1124
int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) {
8,196✔
1125
        _cleanup_free_ char *slice = NULL;
8,196✔
1126
        char *start, *end;
8,196✔
1127
        int r;
8,196✔
1128

1129
        assert(path);
8,196✔
1130

1131
        r = cg_path_get_slice(path, &slice);
8,196✔
1132
        if (r < 0)
8,196✔
1133
                return r;
1134

1135
        start = startswith(slice, "user-");
8,196✔
1136
        if (!start)
8,196✔
1137
                return -ENXIO;
1138

1139
        end = endswith(start, ".slice");
443✔
1140
        if (!end)
443✔
1141
                return -ENXIO;
1142

1143
        *end = 0;
443✔
1144
        if (parse_uid(start, ret_uid) < 0)
443✔
1145
                return -ENXIO;
×
1146

1147
        return 0;
1148
}
1149

1150
int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) {
356✔
1151
        _cleanup_free_ char *cgroup = NULL;
356✔
1152
        int r;
356✔
1153

1154
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
356✔
1155
        if (r < 0)
356✔
1156
                return r;
1157

1158
        return cg_path_get_owner_uid(cgroup, ret_uid);
356✔
1159
}
1160

1161
int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) {
55✔
1162
        int r;
55✔
1163

1164
        if (!pidref_is_set(pidref))
55✔
1165
                return -ESRCH;
55✔
1166
        if (pidref_is_remote(pidref))
55✔
1167
                return -EREMOTE;
1168

1169
        uid_t uid;
55✔
1170
        r = cg_pid_get_owner_uid(pidref->pid, &uid);
55✔
1171
        if (r < 0)
55✔
1172
                return r;
1173

1174
        r = pidref_verify(pidref);
12✔
1175
        if (r < 0)
12✔
1176
                return r;
1177

1178
        if (ret)
12✔
1179
                *ret = uid;
12✔
1180

1181
        return 0;
1182
}
1183

1184
int cg_path_get_slice(const char *p, char **ret_slice) {
16,261✔
1185
        const char *e = NULL;
16,261✔
1186

1187
        assert(p);
16,261✔
1188

1189
        /* Finds the right-most slice unit from the beginning, but stops before we come to
1190
         * the first non-slice unit. */
1191

1192
        for (;;) {
49,747✔
1193
                const char *s;
33,004✔
1194
                int n;
33,004✔
1195

1196
                n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s);
33,004✔
1197
                if (n < 0)
33,004✔
1198
                        return n;
×
1199
                if (!valid_slice_name(s, n))
33,004✔
1200
                        break;
1201

1202
                e = s;
16,743✔
1203
        }
1204

1205
        if (e)
16,261✔
1206
                return cg_path_decode_unit(e, ret_slice);
16,017✔
1207

1208
        if (ret_slice)
244✔
1209
                return strdup_to(ret_slice, SPECIAL_ROOT_SLICE);
244✔
1210

1211
        return 0;
1212
}
1213

1214
int cg_pid_get_slice(pid_t pid, char **ret_slice) {
62✔
1215
        _cleanup_free_ char *cgroup = NULL;
62✔
1216
        int r;
62✔
1217

1218
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
62✔
1219
        if (r < 0)
62✔
1220
                return r;
1221

1222
        return cg_path_get_slice(cgroup, ret_slice);
62✔
1223
}
1224

1225
int cg_path_get_user_slice(const char *p, char **ret_slice) {
7,803✔
1226
        const char *t;
7,803✔
1227
        assert(p);
7,803✔
1228

1229
        t = skip_user_prefix(p);
7,803✔
1230
        if (!t)
7,803✔
1231
                return -ENXIO;
1232

1233
        /* And now it looks pretty much the same as for a system slice, so let's just use the same parser
1234
         * from here on. */
1235
        return cg_path_get_slice(t, ret_slice);
207✔
1236
}
1237

1238
int cg_pid_get_user_slice(pid_t pid, char **ret_slice) {
1✔
1239
        _cleanup_free_ char *cgroup = NULL;
1✔
1240
        int r;
1✔
1241

1242
        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
1✔
1243
        if (r < 0)
1✔
1244
                return r;
1245

1246
        return cg_path_get_user_slice(cgroup, ret_slice);
1✔
1247
}
1248

1249
bool cg_needs_escape(const char *p) {
25,412✔
1250

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

1256
        if (!filename_is_valid(p))
25,412✔
1257
                return true;
1258

1259
        if (IN_SET(p[0], '_', '.'))
25,408✔
1260
                return true;
1261

1262
        if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
25,402✔
1263
                return true;
2✔
1264

1265
        if (startswith(p, "cgroup."))
25,400✔
1266
                return true;
1267

1268
        for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
355,572✔
1269
                const char *q;
330,174✔
1270

1271
                q = startswith(p, cgroup_controller_to_string(c));
330,174✔
1272
                if (!q)
330,174✔
1273
                        continue;
330,174✔
1274

1275
                if (q[0] == '.')
×
1276
                        return true;
1277
        }
1278

1279
        return false;
1280
}
1281

1282
int cg_escape(const char *p, char **ret) {
25,155✔
1283
        _cleanup_free_ char *n = NULL;
25,155✔
1284

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

1290
        /* The return value of this function (unlike cg_unescape()) needs free()! */
1291

1292
        if (cg_needs_escape(p)) {
25,155✔
1293
                n = strjoin("_", p);
7✔
1294
                if (!n)
7✔
1295
                        return -ENOMEM;
1296

1297
                if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
7✔
1298
                        return -EINVAL;
1299
        } else {
1300
                n = strdup(p);
25,148✔
1301
                if (!n)
25,148✔
1302
                        return -ENOMEM;
1303
        }
1304

1305
        *ret = TAKE_PTR(n);
25,155✔
1306
        return 0;
25,155✔
1307
}
1308

1309
char* cg_unescape(const char *p) {
105,292✔
1310
        assert(p);
105,292✔
1311

1312
        /* The return value of this function (unlike cg_escape())
1313
         * doesn't need free()! */
1314

1315
        if (p[0] == '_')
105,292✔
1316
                return (char*) p+1;
14✔
1317

1318
        return (char*) p;
1319
}
1320

1321
int cg_slice_to_path(const char *unit, char **ret) {
10,925✔
1322
        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
10,925✔
1323
        const char *dash;
10,925✔
1324
        int r;
10,925✔
1325

1326
        assert(unit);
10,925✔
1327
        assert(ret);
10,925✔
1328

1329
        if (streq(unit, SPECIAL_ROOT_SLICE))
10,925✔
1330
                return strdup_to(ret, "");
7✔
1331

1332
        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
10,918✔
1333
                return -EINVAL;
1334

1335
        if (!endswith(unit, ".slice"))
10,907✔
1336
                return -EINVAL;
1337

1338
        r = unit_name_to_prefix(unit, &p);
10,906✔
1339
        if (r < 0)
10,906✔
1340
                return r;
1341

1342
        dash = strchr(p, '-');
10,906✔
1343

1344
        /* Don't allow initial dashes */
1345
        if (dash == p)
10,906✔
1346
                return -EINVAL;
1347

1348
        while (dash) {
11,494✔
1349
                _cleanup_free_ char *escaped = NULL;
593✔
1350
                char n[dash - p + sizeof(".slice")];
593✔
1351

1352
#if HAS_FEATURE_MEMORY_SANITIZER
1353
                /* msan doesn't instrument stpncpy, so it thinks
1354
                 * n is later used uninitialized:
1355
                 * https://github.com/google/sanitizers/issues/926
1356
                 */
1357
                zero(n);
1358
#endif
1359

1360
                /* Don't allow trailing or double dashes */
1361
                if (IN_SET(dash[1], 0, '-'))
593✔
1362
                        return -EINVAL;
1363

1364
                strcpy(stpncpy(n, p, dash - p), ".slice");
591✔
1365
                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
591✔
1366
                        return -EINVAL;
1367

1368
                r = cg_escape(n, &escaped);
591✔
1369
                if (r < 0)
591✔
1370
                        return r;
1371

1372
                if (!strextend(&s, escaped, "/"))
591✔
1373
                        return -ENOMEM;
1374

1375
                dash = strchr(dash+1, '-');
591✔
1376
        }
1377

1378
        r = cg_escape(unit, &e);
10,901✔
1379
        if (r < 0)
10,901✔
1380
                return r;
1381

1382
        if (!strextend(&s, e))
10,901✔
1383
                return -ENOMEM;
1384

1385
        *ret = TAKE_PTR(s);
10,901✔
1386
        return 0;
10,901✔
1387
}
1388

1389
int cg_is_threaded(const char *path) {
×
1390
        _cleanup_free_ char *fs = NULL, *contents = NULL;
×
1391
        _cleanup_strv_free_ char **v = NULL;
×
1392
        int r;
×
1393

1394
        r = cg_get_path(path, "cgroup.type", &fs);
×
1395
        if (r < 0)
×
1396
                return r;
1397

1398
        r = read_full_virtual_file(fs, &contents, NULL);
×
1399
        if (r == -ENOENT)
×
1400
                return false; /* Assume no. */
1401
        if (r < 0)
×
1402
                return r;
1403

1404
        v = strv_split(contents, NULL);
×
1405
        if (!v)
×
1406
                return -ENOMEM;
1407

1408
        /* If the cgroup is in the threaded mode, it contains "threaded".
1409
         * If one of the parents or siblings is in the threaded mode, it may contain "invalid". */
1410
        return strv_contains(v, "threaded") || strv_contains(v, "invalid");
×
1411
}
1412

1413
int cg_set_attribute(const char *path, const char *attribute, const char *value) {
48,201✔
1414
        _cleanup_free_ char *p = NULL;
48,201✔
1415
        int r;
48,201✔
1416

1417
        assert(attribute);
48,201✔
1418

1419
        r = cg_get_path(path, attribute, &p);
48,201✔
1420
        if (r < 0)
48,201✔
1421
                return r;
1422

1423
        /* https://lore.kernel.org/all/20250419183545.1982187-1-shakeel.butt@linux.dev/ adds O_NONBLOCK
1424
         * semantics to memory.max and memory.high to skip synchronous memory reclaim when O_NONBLOCK is
1425
         * enabled. Let's always open cgroupv2 attribute files in nonblocking mode to immediately take
1426
         * advantage of this and any other asynchronous resource reclaim that's added to the cgroupv2 API in
1427
         * the future. */
1428
        return write_string_file(p, value, WRITE_STRING_FILE_DISABLE_BUFFER|WRITE_STRING_FILE_OPEN_NONBLOCKING);
48,201✔
1429
}
1430

1431
int cg_get_attribute(const char *path, const char *attribute, char **ret) {
50,478✔
1432
        _cleanup_free_ char *p = NULL;
50,478✔
1433
        int r;
50,478✔
1434

1435
        assert(attribute);
50,478✔
1436

1437
        r = cg_get_path(path, attribute, &p);
50,478✔
1438
        if (r < 0)
50,478✔
1439
                return r;
1440

1441
        return read_one_line_file(p, ret);
50,478✔
1442
}
1443

1444
int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t *ret) {
41,769✔
1445
        _cleanup_free_ char *value = NULL;
41,769✔
1446
        uint64_t v;
41,769✔
1447
        int r;
41,769✔
1448

1449
        assert(ret);
41,769✔
1450

1451
        r = cg_get_attribute(path, attribute, &value);
41,769✔
1452
        if (r == -ENOENT)
41,769✔
1453
                return -ENODATA;
1454
        if (r < 0)
39,850✔
1455
                return r;
1456

1457
        if (streq(value, "max")) {
39,850✔
1458
                *ret = CGROUP_LIMIT_MAX;
10,292✔
1459
                return 0;
10,292✔
1460
        }
1461

1462
        r = safe_atou64(value, &v);
29,558✔
1463
        if (r < 0)
29,558✔
1464
                return r;
1465

1466
        *ret = v;
29,558✔
1467
        return 0;
29,558✔
1468
}
1469

1470
int cg_get_attribute_as_bool(const char *path, const char *attribute) {
59✔
1471
        _cleanup_free_ char *value = NULL;
59✔
1472
        int r;
59✔
1473

1474
        r = cg_get_attribute(path, attribute, &value);
59✔
1475
        if (r == -ENOENT)
59✔
1476
                return -ENODATA;
1477
        if (r < 0)
59✔
1478
                return r;
1479

1480
        return parse_boolean(value);
59✔
1481
}
1482

1483
int cg_get_owner(const char *path, uid_t *ret_uid) {
35✔
1484
        _cleanup_free_ char *f = NULL;
35✔
1485
        struct stat stats;
35✔
1486
        int r;
35✔
1487

1488
        assert(ret_uid);
35✔
1489

1490
        r = cg_get_path(path, /* suffix = */ NULL, &f);
35✔
1491
        if (r < 0)
35✔
1492
                return r;
1493

1494
        if (stat(f, &stats) < 0)
35✔
1495
                return -errno;
16✔
1496

1497
        r = stat_verify_directory(&stats);
19✔
1498
        if (r < 0)
19✔
1499
                return r;
1500

1501
        *ret_uid = stats.st_uid;
19✔
1502
        return 0;
19✔
1503
}
1504

1505
int cg_get_keyed_attribute(
25,832✔
1506
                const char *path,
1507
                const char *attribute,
1508
                char * const *keys,
1509
                char **values) {
1510

1511
        _cleanup_free_ char *filename = NULL, *contents = NULL;
25,832✔
1512
        size_t n;
25,832✔
1513
        int r;
25,832✔
1514

1515
        assert(path);
25,832✔
1516
        assert(attribute);
25,832✔
1517

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

1524
        r = cg_get_path(path, attribute, &filename);
25,832✔
1525
        if (r < 0)
25,832✔
1526
                return r;
1527

1528
        r = read_full_file(filename, &contents, /* ret_size = */ NULL);
25,832✔
1529
        if (r < 0)
25,832✔
1530
                return r;
1531

1532
        n = strv_length(keys);
23,273✔
1533
        if (n == 0) /* No keys to retrieve? That's easy, we are done then */
23,273✔
1534
                return 0;
1535
        assert(strv_is_uniq(keys));
23,273✔
1536

1537
        /* Let's build this up in a temporary array for now in order not to clobber the return parameter on failure */
1538
        char **v = newa0(char*, n);
23,273✔
1539
        size_t n_done = 0;
23,273✔
1540

1541
        for (const char *p = contents; *p;) {
73,867✔
1542
                const char *w;
1543
                size_t i;
1544

1545
                for (i = 0; i < n; i++) {
124,469✔
1546
                        w = first_word(p, keys[i]);
81,604✔
1547
                        if (w)
81,604✔
1548
                                break;
1549
                }
1550

1551
                if (w) {
73,865✔
1552
                        if (v[i]) { /* duplicate entry? */
31,000✔
1553
                                r = -EBADMSG;
×
1554
                                goto fail;
×
1555
                        }
1556

1557
                        size_t l = strcspn(w, NEWLINE);
31,000✔
1558

1559
                        v[i] = strndup(w, l);
31,000✔
1560
                        if (!v[i]) {
31,000✔
1561
                                r = -ENOMEM;
×
1562
                                goto fail;
×
1563
                        }
1564

1565
                        n_done++;
31,000✔
1566
                        if (n_done >= n)
31,000✔
1567
                                break;
1568

1569
                        p = w + l;
7,729✔
1570
                } else
1571
                        p += strcspn(p, NEWLINE);
42,865✔
1572

1573
                p += strspn(p, NEWLINE);
50,594✔
1574
        }
1575

1576
        if (n_done < n) {
23,273✔
1577
                r = -ENXIO;
2✔
1578
                goto fail;
2✔
1579
        }
1580

1581
        memcpy(values, v, sizeof(char*) * n);
23,271✔
1582
        return 0;
23,271✔
1583

1584
fail:
2✔
1585
        free_many_charp(v, n);
25,834✔
1586
        return r;
1587
}
1588

1589
int cg_mask_to_string(CGroupMask mask, char **ret) {
9,917✔
1590
        _cleanup_free_ char *s = NULL;
9,917✔
1591
        bool space = false;
9,917✔
1592
        CGroupController c;
9,917✔
1593
        size_t n = 0;
9,917✔
1594

1595
        assert(ret);
9,917✔
1596

1597
        if (mask == 0) {
9,917✔
1598
                *ret = NULL;
5,682✔
1599
                return 0;
5,682✔
1600
        }
1601

1602
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
59,290✔
1603
                const char *k;
55,055✔
1604
                size_t l;
55,055✔
1605

1606
                if (!FLAGS_SET(mask, CGROUP_CONTROLLER_TO_MASK(c)))
55,055✔
1607
                        continue;
31,848✔
1608

1609
                k = cgroup_controller_to_string(c);
23,207✔
1610
                l = strlen(k);
23,207✔
1611

1612
                if (!GREEDY_REALLOC(s, n + space + l + 1))
23,207✔
1613
                        return -ENOMEM;
1614

1615
                if (space)
23,207✔
1616
                        s[n] = ' ';
18,972✔
1617
                memcpy(s + n + space, k, l);
23,207✔
1618
                n += space + l;
23,207✔
1619

1620
                space = true;
23,207✔
1621
        }
1622

1623
        assert(s);
4,235✔
1624

1625
        s[n] = 0;
4,235✔
1626
        *ret = TAKE_PTR(s);
4,235✔
1627

1628
        return 0;
4,235✔
1629
}
1630

1631
int cg_mask_from_string(const char *s, CGroupMask *ret) {
4,004✔
1632
        CGroupMask m = 0;
4,004✔
1633

1634
        assert(ret);
4,004✔
1635
        assert(s);
4,004✔
1636

1637
        for (;;) {
26,940✔
1638
                _cleanup_free_ char *n = NULL;
22,936✔
1639
                CGroupController v;
26,940✔
1640
                int r;
26,940✔
1641

1642
                r = extract_first_word(&s, &n, NULL, 0);
26,940✔
1643
                if (r < 0)
26,940✔
1644
                        return r;
×
1645
                if (r == 0)
26,940✔
1646
                        break;
1647

1648
                v = cgroup_controller_from_string(n);
22,936✔
1649
                if (v < 0)
22,936✔
1650
                        continue;
762✔
1651

1652
                m |= CGROUP_CONTROLLER_TO_MASK(v);
22,174✔
1653
        }
1654

1655
        *ret = m;
4,004✔
1656
        return 0;
4,004✔
1657
}
1658

1659
int cg_mask_supported_subtree(const char *root, CGroupMask *ret) {
502✔
1660
        CGroupMask mask;
502✔
1661
        int r;
502✔
1662

1663
        /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
1664
         * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
1665
         * pseudo-controllers. */
1666

1667
        /* We can read the supported and accessible controllers from the top-level cgroup attribute */
1668
        _cleanup_free_ char *controllers = NULL, *path = NULL;
502✔
1669
        r = cg_get_path(root, "cgroup.controllers", &path);
502✔
1670
        if (r < 0)
502✔
1671
                return r;
1672

1673
        r = read_one_line_file(path, &controllers);
502✔
1674
        if (r < 0)
502✔
1675
                return r;
1676

1677
        r = cg_mask_from_string(controllers, &mask);
502✔
1678
        if (r < 0)
502✔
1679
                return r;
1680

1681
        /* Mask controllers that are not supported in cgroup v2. */
1682
        mask &= CGROUP_MASK_V2;
502✔
1683

1684
        *ret = mask;
502✔
1685
        return 0;
502✔
1686
}
1687

1688
int cg_mask_supported(CGroupMask *ret) {
248✔
1689
        _cleanup_free_ char *root = NULL;
248✔
1690
        int r;
248✔
1691

1692
        r = cg_get_root_path(&root);
248✔
1693
        if (r < 0)
248✔
1694
                return r;
1695

1696
        return cg_mask_supported_subtree(root, ret);
248✔
1697
}
1698

1699
int cg_is_delegated(const char *path) {
19✔
1700
        int r;
19✔
1701

1702
        assert(path);
19✔
1703

1704
        r = cg_get_xattr_bool(path, "trusted.delegate");
19✔
1705
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
19✔
1706
                return r;
1707

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

1715
int cg_is_delegated_fd(int fd) {
199✔
1716
        int r;
199✔
1717

1718
        assert(fd >= 0);
199✔
1719

1720
        r = getxattr_at_bool(fd, /* path= */ NULL, "trusted.delegate", /* at_flags= */ 0);
199✔
1721
        if (!ERRNO_IS_NEG_XATTR_ABSENT(r))
199✔
1722
                return r;
1723

1724
        r = getxattr_at_bool(fd, /* path= */ NULL, "user.delegate", /* at_flags= */ 0);
185✔
1725
        return ERRNO_IS_NEG_XATTR_ABSENT(r) ? false : r;
185✔
1726
}
1727

1728
int cg_has_coredump_receive(const char *path) {
2✔
1729
        int r;
2✔
1730

1731
        assert(path);
2✔
1732

1733
        r = cg_get_xattr_bool(path, "user.coredump_receive");
2✔
1734
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
2✔
1735
                return false;
×
1736

1737
        return r;
1738
}
1739

1740
const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1741
        [CGROUP_IO_RBPS_MAX]  = CGROUP_LIMIT_MAX,
1742
        [CGROUP_IO_WBPS_MAX]  = CGROUP_LIMIT_MAX,
1743
        [CGROUP_IO_RIOPS_MAX] = CGROUP_LIMIT_MAX,
1744
        [CGROUP_IO_WIOPS_MAX] = CGROUP_LIMIT_MAX,
1745
};
1746

1747
static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] = {
1748
        [CGROUP_IO_RBPS_MAX]  = "IOReadBandwidthMax",
1749
        [CGROUP_IO_WBPS_MAX]  = "IOWriteBandwidthMax",
1750
        [CGROUP_IO_RIOPS_MAX] = "IOReadIOPSMax",
1751
        [CGROUP_IO_WIOPS_MAX] = "IOWriteIOPSMax",
1752
};
1753

1754
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
12,600✔
1755

1756
void cgroup_io_limits_list(void) {
20✔
1757
        DUMP_STRING_TABLE(cgroup_io_limit_type, CGroupIOLimitType, _CGROUP_IO_LIMIT_TYPE_MAX);
100✔
1758
}
20✔
1759

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

1776
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
404,632✔
1777

1778
static const char* const managed_oom_mode_table[_MANAGED_OOM_MODE_MAX] = {
1779
        [MANAGED_OOM_AUTO] = "auto",
1780
        [MANAGED_OOM_KILL] = "kill",
1781
};
1782

1783
DEFINE_STRING_TABLE_LOOKUP(managed_oom_mode, ManagedOOMMode);
31,765✔
1784

1785
static const char* const managed_oom_preference_table[_MANAGED_OOM_PREFERENCE_MAX] = {
1786
        [MANAGED_OOM_PREFERENCE_NONE] = "none",
1787
        [MANAGED_OOM_PREFERENCE_AVOID] = "avoid",
1788
        [MANAGED_OOM_PREFERENCE_OMIT] = "omit",
1789
};
1790

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