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

systemd / systemd / 15232239991

24 May 2025 08:01PM UTC coverage: 72.053% (-0.02%) from 72.07%
15232239991

push

github

web-flow
docs: add man pages for sd_device_enumerator_[new,ref,unref,unrefp] (#37586)

For #20929.

299160 of 415197 relevant lines covered (72.05%)

703671.29 hits per line

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

83.01
/src/shared/cgroup-setup.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <unistd.h>
4

5
#include "cgroup-setup.h"
6
#include "cgroup-util.h"
7
#include "errno-util.h"
8
#include "fd-util.h"
9
#include "fileio.h"
10
#include "format-util.h"
11
#include "fs-util.h"
12
#include "log.h"
13
#include "missing_magic.h"
14
#include "mkdir.h"
15
#include "parse-util.h"
16
#include "path-util.h"
17
#include "process-util.h"
18
#include "recurse-dir.h"
19
#include "set.h"
20
#include "stat-util.h"
21
#include "stdio-util.h"
22
#include "string-util.h"
23
#include "user-util.h"
24

25
int cg_weight_parse(const char *s, uint64_t *ret) {
548✔
26
        uint64_t u;
548✔
27
        int r;
548✔
28

29
        assert(s);
548✔
30
        assert(ret);
548✔
31

32
        if (isempty(s)) {
548✔
33
                *ret = CGROUP_WEIGHT_INVALID;
×
34
                return 0;
×
35
        }
36

37
        r = safe_atou64(s, &u);
548✔
38
        if (r < 0)
548✔
39
                return r;
40

41
        if (u < CGROUP_WEIGHT_MIN || u > CGROUP_WEIGHT_MAX)
548✔
42
                return -ERANGE;
43

44
        *ret = u;
548✔
45
        return 0;
548✔
46
}
47

48
int cg_cpu_weight_parse(const char *s, uint64_t *ret) {
546✔
49
        assert(s);
546✔
50
        assert(ret);
546✔
51

52
        if (streq(s, "idle"))
546✔
53
                return *ret = CGROUP_WEIGHT_IDLE;
×
54

55
        return cg_weight_parse(s, ret);
546✔
56
}
57

58
static int trim_cb(
121,156✔
59
                RecurseDirEvent event,
60
                const char *path,
61
                int dir_fd,
62
                int inode_fd,
63
                const struct dirent *de,
64
                const struct statx *sx,
65
                void *userdata) {
66

67
        /* Failures to delete inner cgroup we ignore (but debug log in case error code is unexpected) */
68
        if (event == RECURSE_DIR_LEAVE &&
121,156✔
69
            de->d_type == DT_DIR &&
608✔
70
            unlinkat(dir_fd, de->d_name, AT_REMOVEDIR) < 0 &&
304✔
71
            !IN_SET(errno, ENOENT, ENOTEMPTY, EBUSY))
242✔
72
                log_debug_errno(errno, "Failed to trim inner cgroup %s, ignoring: %m", path);
1✔
73

74
        return RECURSE_DIR_CONTINUE;
121,156✔
75
}
76

77
int cg_trim(const char *path, bool delete_root) {
6,403✔
78
        _cleanup_free_ char *fs = NULL;
6,403✔
79
        int r;
6,403✔
80

81
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
6,403✔
82
        if (r < 0)
6,403✔
83
                return r;
84

85
        r = recurse_dir_at(
6,403✔
86
                        AT_FDCWD,
87
                        fs,
88
                        /* statx_mask = */ 0,
89
                        /* n_depth_max = */ UINT_MAX,
90
                        RECURSE_DIR_ENSURE_TYPE,
91
                        trim_cb,
92
                        /* userdata = */ NULL);
93
        if (r == -ENOENT) /* non-existing is the ultimate trimming, hence no error */
6,403✔
94
                r = 0;
95
        else if (r < 0)
2,434✔
96
                log_debug_errno(r, "Failed to trim subcgroups of '%s': %m", path);
×
97

98
        /* If we shall delete the top-level cgroup, then propagate the failure to do so (except if it is
99
         * already gone anyway). Also, let's debug log about this failure, except if the error code is an
100
         * expected one. */
101
        if (delete_root && !empty_or_root(path) &&
12,615✔
102
            rmdir(fs) < 0 && errno != ENOENT) {
10,183✔
103
                if (!IN_SET(errno, ENOTEMPTY, EBUSY))
2✔
104
                        log_debug_errno(errno, "Failed to trim cgroup '%s': %m", path);
×
105
                RET_GATHER(r, -errno);
2✔
106
        }
107

108
        return r;
109
}
110

111
/* Create a cgroup in the hierarchy of controller.
112
 * Returns 0 if the group already existed, 1 on success, negative otherwise.
113
 */
114
int cg_create(const char *path) {
4,526✔
115
        _cleanup_free_ char *fs = NULL;
4,526✔
116
        int r;
4,526✔
117

118
        r = cg_get_path_and_check(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
4,526✔
119
        if (r < 0)
4,526✔
120
                return r;
121

122
        r = mkdir_parents(fs, 0755);
4,526✔
123
        if (r < 0)
4,526✔
124
                return r;
125

126
        r = RET_NERRNO(mkdir(fs, 0755));
4,526✔
127
        if (r == -EEXIST)
1,575✔
128
                return 0;
129
        if (r < 0)
2,951✔
130
                return r;
×
131

132
        return 1;
133
}
134

135
int cg_attach(const char *path, pid_t pid) {
12,026✔
136
        _cleanup_free_ char *fs = NULL;
12,026✔
137
        char c[DECIMAL_STR_MAX(pid_t) + 2];
12,026✔
138
        int r;
12,026✔
139

140
        assert(path);
12,026✔
141
        assert(pid >= 0);
12,026✔
142

143
        r = cg_get_path_and_check(SYSTEMD_CGROUP_CONTROLLER, path, "cgroup.procs", &fs);
12,026✔
144
        if (r < 0)
12,026✔
145
                return r;
146

147
        if (pid == 0)
12,026✔
148
                pid = getpid_cached();
11,875✔
149

150
        xsprintf(c, PID_FMT "\n", pid);
12,026✔
151

152
        r = write_string_file(fs, c, WRITE_STRING_FILE_DISABLE_BUFFER);
12,026✔
153
        if (r == -EOPNOTSUPP && cg_is_threaded(path) > 0)
12,026✔
154
                /* When the threaded mode is used, we cannot read/write the file. Let's return recognizable error. */
155
                return -EUCLEAN;
156
        if (r < 0)
12,026✔
157
                return r;
3✔
158

159
        return 0;
160
}
161

162
int cg_fd_attach(int fd, pid_t pid) {
7✔
163
        char c[DECIMAL_STR_MAX(pid_t) + 2];
7✔
164

165
        assert(fd >= 0);
7✔
166
        assert(pid >= 0);
7✔
167

168
        if (pid == 0)
7✔
169
                pid = getpid_cached();
×
170

171
        xsprintf(c, PID_FMT "\n", pid);
7✔
172

173
        return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER);
7✔
174
}
175

176
int cg_create_and_attach(const char *path, pid_t pid) {
417✔
177
        int r, q;
417✔
178

179
        /* This does not remove the cgroup on failure */
180

181
        assert(pid >= 0);
417✔
182

183
        r = cg_create(path);
417✔
184
        if (r < 0)
417✔
185
                return r;
186

187
        q = cg_attach(path, pid);
417✔
188
        if (q < 0)
417✔
189
                return q;
×
190

191
        return r;
192
}
193

194
int cg_set_access(
672✔
195
                const char *path,
196
                uid_t uid,
197
                gid_t gid) {
198

199
        static const struct {
672✔
200
                const char *name;
201
                bool fatal;
202
        } attributes[] = {
203
                { "cgroup.procs",           true  },
204
                { "cgroup.subtree_control", true  },
205
                { "cgroup.threads",         false },
206
                { "memory.oom.group",       false },
207
                { "memory.reclaim",         false },
208
        };
209

210
        _cleanup_free_ char *fs = NULL;
672✔
211
        int r;
672✔
212

213
        assert(path);
672✔
214

215
        if (uid == UID_INVALID && gid == GID_INVALID)
672✔
216
                return 0;
217

218
        /* Configure access to the cgroup itself */
219
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
191✔
220
        if (r < 0)
191✔
221
                return r;
222

223
        r = chmod_and_chown(fs, 0755, uid, gid);
191✔
224
        if (r < 0)
191✔
225
                return r;
226

227
        /* Configure access to the cgroup's attributes */
228
        FOREACH_ELEMENT(i, attributes) {
1,146✔
229
                _cleanup_free_ char *a = path_join(fs, i->name);
1,910✔
230
                if (!a)
955✔
231
                        return -ENOMEM;
232

233
                r = chmod_and_chown(a, 0644, uid, gid);
955✔
234
                if (r < 0) {
955✔
235
                        if (i->fatal)
×
236
                                return r;
237

238
                        log_debug_errno(r, "Failed to set access on cgroup %s, ignoring: %m", a);
955✔
239
                }
240
        }
241

242
        return 0;
243
}
244

245
struct access_callback_data {
246
        uid_t uid;
247
        gid_t gid;
248
        int error;
249
};
250

251
static int access_callback(
3,504✔
252
                RecurseDirEvent event,
253
                const char *path,
254
                int dir_fd,
255
                int inode_fd,
256
                const struct dirent *de,
257
                const struct statx *sx,
258
                void *userdata) {
259

260
        if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
3,504✔
261
                return RECURSE_DIR_CONTINUE;
262

263
        struct access_callback_data *d = ASSERT_PTR(userdata);
3,324✔
264

265
        assert(path);
3,324✔
266
        assert(inode_fd >= 0);
3,324✔
267

268
        if (fchownat(inode_fd, "", d->uid, d->gid, AT_EMPTY_PATH) < 0)
3,324✔
269
                RET_GATHER(d->error, log_debug_errno(errno, "Failed to change ownership of '%s', ignoring: %m", path));
×
270

271
        return RECURSE_DIR_CONTINUE;
272
}
273

274
int cg_set_access_recursive(
329✔
275
                const char *path,
276
                uid_t uid,
277
                gid_t gid) {
278

279
        _cleanup_close_ int fd = -EBADF;
329✔
280
        _cleanup_free_ char *fs = NULL;
329✔
281
        int r;
329✔
282

283
        assert(path);
329✔
284

285
        /* A recursive version of cg_set_access(). But note that this one changes ownership of *all* files,
286
         * not just the allowlist that cg_set_access() uses. Use cg_set_access() on the cgroup you want to
287
         * delegate, and cg_set_access_recursive() for any subcgroups you might want to create below it. */
288

289
        if (!uid_is_valid(uid) && !gid_is_valid(gid))
478✔
290
                return 0;
291

292
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
180✔
293
        if (r < 0)
180✔
294
                return r;
295

296
        fd = open(fs, O_DIRECTORY|O_CLOEXEC);
180✔
297
        if (fd < 0)
180✔
298
                return -errno;
×
299

300
        struct access_callback_data d = {
180✔
301
                .uid = uid,
302
                .gid = gid,
303
        };
304

305
        r = recurse_dir(fd,
180✔
306
                        fs,
307
                        /* statx_mask= */ 0,
308
                        /* n_depth_max= */ UINT_MAX,
309
                        RECURSE_DIR_SAME_MOUNT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL,
310
                        access_callback,
311
                        &d);
312
        if (r < 0)
180✔
313
                return r;
314

315
        assert(d.error <= 0);
180✔
316
        return d.error;
317
}
318

319
int cg_migrate(
250✔
320
                const char *from,
321
                const char *to,
322
                CGroupFlags flags) {
323

324
        _cleanup_set_free_ Set *s = NULL;
250✔
325
        bool done;
250✔
326
        int r, ret = 0;
250✔
327

328
        assert(from);
250✔
329
        assert(to);
250✔
330

331
        do {
250✔
332
                _cleanup_fclose_ FILE *f = NULL;
×
333
                pid_t pid;
250✔
334

335
                done = true;
250✔
336

337
                r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, from, &f);
250✔
338
                if (r < 0)
250✔
339
                        return RET_GATHER(ret, r);
×
340

341
                while ((r = cg_read_pid(f, &pid, flags)) > 0) {
2,007✔
342
                        /* Throw an error if unmappable PIDs are in output, we can't migrate those. */
343
                        if (pid == 0)
1,757✔
344
                                return -EREMOTE;
345

346
                        /* This might do weird stuff if we aren't a single-threaded program. However, we
347
                         * luckily know we are. */
348
                        if (FLAGS_SET(flags, CGROUP_IGNORE_SELF) && pid == getpid_cached())
1,757✔
349
                                continue;
×
350

351
                        if (set_contains(s, PID_TO_PTR(pid)))
1,757✔
352
                                continue;
×
353

354
                        if (pid_is_kernel_thread(pid) > 0)
1,757✔
355
                                continue;
1,757✔
356

357
                        r = cg_attach(to, pid);
×
358
                        if (r < 0) {
×
359
                                if (r != -ESRCH)
×
360
                                        RET_GATHER(ret, r);
×
361
                        } else if (ret == 0)
×
362
                                ret = 1;
×
363

364
                        done = false;
×
365

366
                        r = set_ensure_put(&s, /* hash_ops = */ NULL, PID_TO_PTR(pid));
×
367
                        if (r < 0)
×
368
                                return RET_GATHER(ret, r);
×
369
                }
370
                if (r < 0)
250✔
371
                        return RET_GATHER(ret, r);
×
372
        } while (!done);
250✔
373

374
        return ret;
375
}
376

377
int cg_enable(
4,178✔
378
                CGroupMask supported,
379
                CGroupMask mask,
380
                const char *p,
381
                CGroupMask *ret_result_mask) {
382

383
        _cleanup_fclose_ FILE *f = NULL;
4,178✔
384
        _cleanup_free_ char *fs = NULL;
4,178✔
385
        CGroupController c;
4,178✔
386
        CGroupMask ret = 0;
4,178✔
387
        int r;
4,178✔
388

389
        assert(p);
4,178✔
390

391
        if (supported == 0) {
4,178✔
392
                if (ret_result_mask)
23✔
393
                        *ret_result_mask = 0;
23✔
394
                return 0;
23✔
395
        }
396

397
        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs);
4,155✔
398
        if (r < 0)
4,155✔
399
                return r;
400

401
        for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
58,170✔
402
                CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
54,015✔
403
                const char *n;
54,015✔
404

405
                if (!FLAGS_SET(CGROUP_MASK_V2, bit))
54,015✔
406
                        continue;
33,240✔
407

408
                if (!FLAGS_SET(supported, bit))
20,775✔
409
                        continue;
3,128✔
410

411
                n = cgroup_controller_to_string(c);
17,647✔
412
                {
17,647✔
413
                        char s[1 + strlen(n) + 1];
17,647✔
414

415
                        s[0] = FLAGS_SET(mask, bit) ? '+' : '-';
17,647✔
416
                        strcpy(s + 1, n);
17,647✔
417

418
                        if (!f) {
17,647✔
419
                                f = fopen(fs, "we");
4,155✔
420
                                if (!f)
4,155✔
421
                                        return log_debug_errno(errno, "Failed to open cgroup.subtree_control file of %s: %m", p);
×
422
                        }
423

424
                        r = write_string_stream(f, s, WRITE_STRING_FILE_DISABLE_BUFFER);
17,647✔
425
                        if (r < 0) {
17,647✔
426
                                log_debug_errno(r, "Failed to %s controller %s for %s (%s): %m",
6✔
427
                                                FLAGS_SET(mask, bit) ? "enable" : "disable", n, p, fs);
428
                                clearerr(f);
6✔
429

430
                                /* If we can't turn off a controller, leave it on in the reported resulting mask. This
431
                                 * happens for example when we attempt to turn off a controller up in the tree that is
432
                                 * used down in the tree. */
433
                                if (!FLAGS_SET(mask, bit) && r == -EBUSY) /* You might wonder why we check for EBUSY
6✔
434
                                                                           * only here, and not follow the same logic
435
                                                                           * for other errors such as EINVAL or
436
                                                                           * EOPNOTSUPP or anything else. That's
437
                                                                           * because EBUSY indicates that the
438
                                                                           * controllers is currently enabled and
439
                                                                           * cannot be disabled because something down
440
                                                                           * the hierarchy is still using it. Any other
441
                                                                           * error most likely means something like "I
442
                                                                           * never heard of this controller" or
443
                                                                           * similar. In the former case it's hence
444
                                                                           * safe to assume the controller is still on
445
                                                                           * after the failed operation, while in the
446
                                                                           * latter case it's safer to assume the
447
                                                                           * controller is unknown and hence certainly
448
                                                                           * not enabled. */
449
                                        ret |= bit;
×
450
                        } else {
451
                                /* Otherwise, if we managed to turn on a controller, set the bit reflecting that. */
452
                                if (FLAGS_SET(mask, bit))
17,641✔
453
                                        ret |= bit;
3,724✔
454
                        }
455
                }
456
        }
457

458
        /* Let's return the precise set of controllers now enabled for the cgroup. */
459
        if (ret_result_mask)
4,155✔
460
                *ret_result_mask = ret;
4,017✔
461

462
        return 0;
463
}
464

465
int cg_has_legacy(void) {
694✔
466
        struct statfs fs;
694✔
467

468
        /* Checks if any legacy controller/hierarchy is mounted. */
469

470
        if (statfs("/sys/fs/cgroup/", &fs) < 0) {
694✔
471
                if (errno == ENOENT) /* sysfs not mounted? */
×
472
                        return false;
694✔
473

474
                return log_error_errno(errno, "Failed to statfs /sys/fs/cgroup/: %m");
×
475
        }
476

477
        if (is_fs_type(&fs, CGROUP2_SUPER_MAGIC) ||
694✔
478
            is_fs_type(&fs, SYSFS_MAGIC)) /* not mounted yet */
×
479
                return false;
480

481
        if (is_fs_type(&fs, TMPFS_MAGIC)) {
×
482
                log_info("Found tmpfs on /sys/fs/cgroup/, assuming legacy hierarchy.");
×
483
                return true;
×
484
        }
485

486
        return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
×
487
                               "Unknown filesystem type %llx mounted on /sys/fs/cgroup/.",
488
                               (unsigned long long) fs.f_type);
489
}
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