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

drakenclimber / libcgroup / 14204181491

01 Apr 2025 07:18PM UTC coverage: 48.536% (-7.8%) from 56.331%
14204181491

push

github

drakenclimber
googletest: Update to use a newer version

Signed-off-by: Tom Hromatka <tom.hromatka@oracle.com>

4891 of 10077 relevant lines covered (48.54%)

248.96 hits per line

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

2.69
/src/systemd.c
1
/* SPDX-License-Identifier: LGPL-2.1-only */
2
/**
3
 * Copyright (c) 2022 Oracle and/or its affiliates.
4
 * Author: Tom Hromatka <tom.hromatka@oracle.com>
5
 * Author: Silvia Chapa <silvia.chapa@oracle.com>
6
 */
7

8
#include <libcgroup-internal.h>
9

10
#ifdef WITH_SYSTEMD
11
#include <systemd/sd-bus.h>
12
#include <libcgroup.h>
13
#include <unistd.h>
14
#include <assert.h>
15
#include <stdlib.h>
16
#include <libgen.h>
17
#include <errno.h>
18

19
#define USEC_PER_SEC 1000000
20

21
static const char * const modes[] = {
22
        "fail",                        /* CGROUP_SYSTEMD_MODE_FAIL */
23
        "replace",                /* CGROUP_SYSTEMD_MODE_REPLACE */
24
        "isolate",                /* CGROUP_SYSTEMD_MODE_ISOLATE */
25
        "ignore-dependencies",        /* CGROUP_SYSTEMD_MODE_IGNORE_DEPS */
26
        "ignore-requirements",        /* CGROUP_SYSTEMD_MODE_IGNORE_REQS */
27
};
28
static_assert(ARRAY_SIZE(modes) == CGROUP_SYSTEMD_MODE_CNT,
29
              "modes[] array must be same length as CGROUP_SYSTEMD_MODE_CNT");
30

31
/*
32
 * controller_name table borrowed from:
33
 * https://github.com/systemd/systemd/blob/main/src/basic/cgroup-util.c#L2260
34
 */
35
static const char * const controller_name[] = {
36
        /* subset of controllers (cgroup v1/v2), systemd supports */
37
        "cpu",
38
        "cpuacct",
39
        "cpuset",
40
        "io",
41
        "blkio",
42
        "memory",
43
        "devices",
44
        "pids",
45

46
        /* systemd pseudo BPF based controllers (cgroup v2 ) */
47
        "bpf-firewall",
48
        "bpf-devices",
49
        "bpf-foreign",
50
        "bpf-socket-bind",
51
        "bpf-restrict-network-interfaces",
52
};
53

54
static const char * const sender = "org.freedesktop.systemd1";
55
static const char * const path = "/org/freedesktop/systemd1";
56
static const char * const interface = "org.freedesktop.systemd1.Manager";
57

58
int cgroup_set_default_scope_opts(struct cgroup_systemd_scope_opts * const opts)
6✔
59
{
60
        if (!opts)
6✔
61
                return ECGINVAL;
×
62

63
        opts->delegated = 1;
6✔
64
        opts->mode = CGROUP_SYSTEMD_MODE_FAIL;
6✔
65
        opts->pid = -1;
6✔
66

67
        return 0;
6✔
68
}
69

70
/*
71
 * Returns time elapsed in usec
72
 *
73
 * Inspired-by: https://github.com/cockpit-project/cockpit/blob/main/src/tls/socket-io.c#L39
74
 */
75
static int64_t elapsed_time(const struct timespec * const start, const struct timespec * const end)
×
76
{
77
        int64_t elapsed = (end->tv_sec - start->tv_sec) * 1000000 +
×
78
                          (end->tv_nsec - start->tv_nsec) / 1000;
×
79

80
        assert(elapsed >= 0);
×
81

82
        return elapsed;
×
83
}
84

85
static int job_removed_callback(sd_bus_message *message, void *user_data, sd_bus_error *error)
×
86
{
87
        const char *result, *msg_path, *scope_name;
88
        const char **job_path = user_data;
×
89
        int ret;
90

91
        ret = sd_bus_message_read(message, "uoss", NULL, &msg_path, &scope_name, &result);
×
92
        if (ret < 0) {
×
93
                cgroup_err("callback message read failed: %d\n", errno);
×
94
                return 0;
×
95
        }
96

97
        if (*job_path == NULL || strcmp(msg_path, *job_path) != 0) {
×
98
                cgroup_dbg("Received a systemd signal, but it was not our message\n");
×
99
                return 0;
×
100
        }
101

102
        cgroup_dbg("Received JobRemoved signal for scope %s.  Result: %s\n", scope_name, result);
×
103

104
        /*
105
         * Use the job_path pointer as a way to inform the original thread that the job has
106
         * completed.
107
         */
108
        *job_path = NULL;
×
109
        return 0;
×
110
}
111

112
static int validate_scope_slice_name(const char * const scope_name, const char * const slice_name)
×
113
{
114
        char *_scope_name = NULL;
×
115
        int i, len, ret = 0;
×
116

117
        /* get the scope name without the suffix, min length is <n>.scope */
118
        len = strlen(scope_name) - 6;
×
119
        if (len < 1)
×
120
                cgroup_warn("Invalid scope name %s\n", scope_name);
×
121
        else if (strcmp(&scope_name[len], ".scope") != 0)
×
122
                cgroup_warn("scope doesn't have expected suffix\n");
×
123

124
        /* get the slice name without the suffix, min length is <n>.slice */
125
        len = strlen(slice_name) - 6;
×
126
        if (len < 1)
×
127
                cgroup_warn("Invalid slice name %s\n", slice_name);
×
128
        else if (strcmp(&slice_name[len], ".slice") != 0)
×
129
                cgroup_warn("slice doesn't have expected suffix\n");
×
130

131
        /*
132
         * systemd will silently prefix a '_' to the scope name and
133
         * create, and delegate it under the slice. If it matches with any
134
         * of the controller in the controller_name[]. i.e.,
135
         * oracle.slice/cpuset.scope, will be created as
136
         * oracle.slice/_cpuset.scope
137
         *
138
         * Disallow such scope names, that will cause confusion to the
139
         * users, where they might try to operate scope cgroup, but
140
         * the delegated scope is _scope cgroup.
141
         */
142
        _scope_name = strdup(scope_name);
×
143
        if (_scope_name == NULL) {
×
144
                cgroup_err("Failed to allocate memory for _scope_name\n");
×
145
                last_errno = errno;
×
146
                ret = 1;
×
147
                goto out;
×
148
        }
149

150
        len = strlen(_scope_name) - 6;        /* strlen(".scope") + '\0' */
×
151
        _scope_name[len] = '\0';
×
152

153
        for (i = 0; i < ARRAY_SIZE(controller_name); i++) {
×
154
                if (strcmp(_scope_name, controller_name[i]))
×
155
                        continue;
×
156

157
                cgroup_err("Invalid scope name, using controller name %s\n", controller_name[i]);
×
158
                ret = 1;
×
159
                break;
×
160
        }
161

162
        free(_scope_name);
×
163
out:
×
164
        return ret;
×
165
}
166

167
int cgroup_create_scope(const char * const scope_name, const char * const slice_name,
×
168
                        const struct cgroup_systemd_scope_opts * const opts)
169
{
170
        sd_bus_message *msg = NULL, *reply = NULL;
×
171
        int ret = 0, sdret = 0, cgret = ECGFAIL;
×
172
        sd_bus_error error = SD_BUS_ERROR_NULL;
×
173
        const char *job_path = NULL;
×
174
        struct timespec start, now;
175
        sd_bus *bus = NULL;
×
176
        pid_t child_pid;
177

178
        if (!scope_name || !slice_name || !opts)
×
179
                return ECGINVAL;
×
180

181
        if (validate_scope_slice_name(scope_name, slice_name))
×
182
                return ECGINVAL;
×
183

184
        if (opts->mode >= CGROUP_SYSTEMD_MODE_CNT) {
×
185
                cgroup_err("invalid systemd mode: %d\n", opts->mode);
×
186
                return ECGINVAL;
×
187
        }
188

189
        if (opts->mode == CGROUP_SYSTEMD_MODE_ISOLATE ||
×
190
            opts->mode == CGROUP_SYSTEMD_MODE_IGNORE_DEPS ||
×
191
            opts->mode == CGROUP_SYSTEMD_MODE_IGNORE_REQS) {
×
192
                cgroup_err("unsupported systemd mode: %d\n", opts->mode);
×
193
                return ECGINVAL;
×
194
        }
195

196
        if (opts->pid < 0) {
×
197
                child_pid = fork();
×
198
                if (child_pid < 0) {
×
199
                        last_errno = errno;
×
200
                        cgroup_err("fork failed: %d\n", errno);
×
201
                        return ECGOTHER;
×
202
                }
203

204
                if (child_pid == 0) {
×
205
                        static char * const args[] = {"libcgroup_systemd_idle_thread", NULL};
206

207
                        /*
208
                         * Have the child sleep forever.  Systemd will delete the scope if
209
                         * there isn't a running process in it.
210
                         */
211
                        execvp("libcgroup_systemd_idle_thread", args);
×
212

213
                        /* The child process should never get here */
214
                        last_errno = errno;
×
215
                        cgroup_err("failed to create system idle thread.\n");
×
216
                        return ECGOTHER;
×
217
                }
218

219
                cgroup_dbg("created libcgroup_system_idle thread pid %d\n", child_pid);
×
220
        } else {
221
                child_pid = opts->pid;
×
222
        }
223
        cgroup_dbg("pid %d will be placed in scope %s\n", child_pid, scope_name);
×
224

225
        sdret = sd_bus_default_system(&bus);
×
226
        if (sdret < 0) {
×
227
                cgroup_err("failed to open the system bus: %d\n", errno);
×
228
                goto out;
×
229
        }
230

231
        sdret = sd_bus_match_signal(bus, NULL, sender, path, interface,
×
232
                                    "JobRemoved", job_removed_callback, &job_path);
233
        if (sdret < 0) {
×
234
                cgroup_err("failed to install match callback: %d\n", errno);
×
235
                goto out;
×
236
        }
237

238
        sdret = sd_bus_message_new_method_call(bus, &msg, sender, path, interface,
×
239
                                               "StartTransientUnit");
240
        if (sdret < 0) {
×
241
                cgroup_err("failed to create the systemd msg: %d\n", errno);
×
242
                goto out;
×
243
        }
244

245
        sdret = sd_bus_message_append(msg, "ss", scope_name, modes[opts->mode]);
×
246
        if (sdret < 0) {
×
247
                cgroup_err("failed to append the scope name: %d\n", errno);
×
248
                goto out;
×
249
        }
250

251
        sdret = sd_bus_message_open_container(msg, 'a', "(sv)");
×
252
        if (sdret < 0) {
×
253
                cgroup_err("failed to open container: %d\n", errno);
×
254
                goto out;
×
255
        }
256

257
        sdret = sd_bus_message_append(msg, "(sv)", "Description", "s",
×
258
                                      "scope created by libcgroup");
259
        if (sdret < 0) {
×
260
                cgroup_err("failed to append the description: %d\n", errno);
×
261
                goto out;
×
262
        }
263

264
        sdret = sd_bus_message_append(msg, "(sv)", "PIDs", "au", 1, child_pid);
×
265
        if (sdret < 0) {
×
266
                cgroup_err("failed to append the PID: %d\n", errno);
×
267
                goto out;
×
268
        }
269

270
        sdret = sd_bus_message_append(msg, "(sv)", "Slice", "s", slice_name);
×
271
        if (sdret < 0) {
×
272
                cgroup_err("failed to append the slice: %d\n", errno);
×
273
                goto out;
×
274
        }
275

276
        if (opts->delegated == 1) {
×
277
                sdret = sd_bus_message_append(msg, "(sv)", "Delegate", "b", 1);
×
278
                if (sdret < 0) {
×
279
                        cgroup_err("failed to append delegate: %d\n", errno);
×
280
                        goto out;
×
281
                }
282
        }
283

284
        sdret = sd_bus_message_close_container(msg);
×
285
        if (sdret < 0) {
×
286
                cgroup_err("failed to close the container: %d\n", errno);
×
287
                goto out;
×
288
        }
289

290
        sdret = sd_bus_message_append(msg, "a(sa(sv))", 0);
×
291
        if (sdret < 0) {
×
292
                cgroup_err("failed to append aux structure: %d\n", errno);
×
293
                goto out;
×
294
        }
295

296
        sdret = sd_bus_call(bus, msg, 0, &error, &reply);
×
297
        if (sdret < 0) {
×
298
                cgroup_err("sd_bus_call() failed: %d\n",
×
299
                           sd_bus_message_get_errno(msg));
300
                cgroup_err("error message: %s\n", error.message);
×
301
                goto out;
×
302
        }
303

304
        /* Receive the job_path from systemd */
305
        sdret = sd_bus_message_read(reply, "o", &job_path);
×
306
        if (sdret < 0) {
×
307
                cgroup_err("failed to read reply: %d\n", errno);
×
308
                goto out;
×
309
        }
310

311
        cgroup_dbg("job_path = %s\n", job_path);
×
312

313
        ret = clock_gettime(CLOCK_MONOTONIC, &start);
×
314
        if (ret < 0) {
×
315
                last_errno = errno;
×
316
                cgroup_err("Failed to get time: %d\n", errno);
×
317
                cgret = ECGOTHER;
×
318
                goto out;
×
319
        }
320

321
        /* The callback will null out the job_path pointer on completion */
322
        while (job_path) {
×
323
                sdret = sd_bus_process(bus, NULL);
×
324
                if (sdret < 0) {
×
325
                        cgroup_err("failed to process the sd bus: %d\n", errno);
×
326
                        goto out;
×
327
                }
328

329
                if (sdret == 0) {
×
330
                        /*
331
                         * Per the sd_bus_wait() man page, call this function after sd_bus_process
332
                         * returns zero. The wait time (usec) was somewhat arbitrarily chosen
333
                         */
334
                        sdret = sd_bus_wait(bus, 10);
×
335
                        if (sdret < 0) {
×
336
                                cgroup_err("failed to wait for sd bus: %d\n", errno);
×
337
                                goto out;
×
338
                        }
339
                }
340

341
                ret = clock_gettime(CLOCK_MONOTONIC, &now);
×
342
                if (ret < 0) {
×
343
                        last_errno = errno;
×
344
                        cgroup_err("Failed to get time: %d\n", errno);
×
345
                        cgret = ECGOTHER;
×
346
                        goto out;
×
347
                }
348

349
                if (elapsed_time(&start, &now) > USEC_PER_SEC) {
×
350
                        cgroup_err("The create scope command timed out\n");
×
351
                        goto out;
×
352
                }
353
        }
354

355
        cgret = 0;
×
356

357
out:
×
358
        if (cgret && opts->pid < 0)
×
359
                kill(child_pid, SIGTERM);
×
360

361
        sd_bus_error_free(&error);
×
362
        sd_bus_message_unref(msg);
×
363
        sd_bus_message_unref(reply);
×
364
        sd_bus_unref(bus);
×
365

366
        return cgret;
×
367
}
368

369
int cgroup_create_scope2(struct cgroup *cgroup, int ignore_ownership,
×
370
                         const struct cgroup_systemd_scope_opts * const opts)
371
{
372
        char *copy1 = NULL, *copy2 = NULL, *slash, *slice_name, *scope_name;
×
373
        int ret = 0;
×
374

375
        if (!cgroup)
×
376
                return ECGROUPNOTALLOWED;
×
377

378
        slash = strstr(cgroup->name, "/");
×
379
        if (!slash) {
×
380
                cgroup_err("cgroup name does not contain a slash: %s\n", cgroup->name);
×
381
                return ECGINVAL;
×
382
        }
383

384
        slash = strstr(slash + 1, "/");
×
385
        if (slash) {
×
386
                cgroup_err("cgroup name contains more than one slash: %s\n", cgroup->name);
×
387
                return ECGINVAL;
×
388
        }
389

390
        copy1 = strdup(cgroup->name);
×
391
        if (!copy1) {
×
392
                last_errno = errno;
×
393
                ret = ECGOTHER;
×
394
                goto err;
×
395
        }
396

397
        scope_name = basename(copy1);
×
398

399
        copy2 = strdup(cgroup->name);
×
400
        if (!copy2) {
×
401
                last_errno = errno;
×
402
                ret = ECGOTHER;
×
403
                goto err;
×
404
        }
405

406
        slice_name = dirname(copy2);
×
407

408
        ret = cgroup_create_scope(scope_name, slice_name, opts);
×
409
        if (ret)
×
410
                goto err;
×
411

412
        /*
413
         * Utilize cgroup_create_cgroup() to assign the requested owner/group and permissions.
414
         * cgroup_create_cgroup() can gracefully handle EEXIST if the cgroup already exists, so
415
         * we can reuse its ownership logic without penalty.
416
         */
417
        ret = cgroup_create_cgroup(cgroup, ignore_ownership);
×
418

419
err:
×
420
        if (copy1)
×
421
                free(copy1);
×
422
        if (copy2)
×
423
                free(copy2);
×
424

425
        return ret;
×
426
}
427

428
bool cgroup_is_systemd_enabled(void)
×
429
{
430
        return true;
×
431
}
432
#else
433
int cgroup_set_default_scope_opts(struct cgroup_systemd_scope_opts * const opts)
×
434
{
435
        cgroup_err("Systemd support not compiled\n");
×
436
        return 1;
×
437
}
438

439
int cgroup_create_scope(const char * const scope_name, const char * const slice_name,
×
440
                        const struct cgroup_systemd_scope_opts * const opts)
441
{
442
        cgroup_err("Systemd support not compiled\n");
×
443
        return 1;
×
444
}
445

446
int cgroup_create_scope2(struct cgroup *cgroup, int ignore_ownership,
×
447
                         const struct cgroup_systemd_scope_opts * const opts)
448
{
449
        cgroup_err("Systemd support not compiled\n");
×
450
        return 1;
×
451
}
452

453
bool cgroup_is_systemd_enabled(void)
×
454
{
455
        return false;
×
456
}
457
#endif
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