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

drakenclimber / libcgroup / 14885439003

25 Apr 2025 09:50AM UTC coverage: 56.161% (+0.07%) from 56.088%
14885439003

push

github

kamalesh-babulal
ftests: Add a test for the memory abstraction layer

Add a test for the memory abstraction layer abstractions.
Currently only supports:
            memory.max <-> memory.limit_in_bytes
            memory.high <-> memory.soft_limit_in_bytes

-----------------------------------------------------------------
Test Results:
        Run Date:                          Apr 14 14:52:47
        Passed:                                  1 test(s)
        Skipped:                                 0 test(s)
        Failed:                                  0 test(s)
-----------------------------------------------------------------
Timing Results:
        Test                              Time (sec)
        --------------------------------------------
        setup                                   0.00
        093-cgxget-memory_settings.py           3.34
        teardown                                0.00
        --------------------------------------------
        Total Run Time                          3.34

[Kamalesh removed the unused exceptions variable (lint warnings)]
Signed-off-by: Tom Hromatka <tom.hromatka@oracle.com>
Signed-off-by: Kamalesh Babulal <kamalesh.babulal@oracle.com>

5597 of 9966 relevant lines covered (56.16%)

560.5 hits per line

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

61.79
/src/api.c
1
// SPDX-License-Identifier: LGPL-2.1-only
2
/**
3
 * Copyright IBM Corporation. 2007
4
 *
5
 * Author:        Dhaval Giani <dhaval@linux.vnet.ibm.com>
6
 * Author:        Balbir Singh <balbir@linux.vnet.ibm.com>
7
 *
8
 * TODOs:
9
 *        1. Add more APIs for the control groups.
10
 *        2. Handle the configuration related APIs.
11
 *
12
 * Code initiated and designed by Dhaval Giani. All faults are most likely
13
 * his mistake.
14
 *
15
 * Bharata B Rao <bharata@linux.vnet.ibm.com> is willing is take blame
16
 * for mistakes in APIs for reading statistics.
17
 */
18

19
#ifndef _GNU_SOURCE
20
#define _GNU_SOURCE
21
#endif
22

23
#include <libcgroup.h>
24
#include <libcgroup-internal.h>
25

26
#include <pthread.h>
27
#include <dirent.h>
28
#include <unistd.h>
29
#include <mntent.h>
30
#include <stdlib.h>
31
#include <string.h>
32
#include <libgen.h>
33
#include <assert.h>
34
#include <errno.h>
35
#include <stdio.h>
36
#include <fcntl.h>
37
#include <ctype.h>
38
#include <fts.h>
39
#include <pwd.h>
40
#include <grp.h>
41

42
#include <sys/syscall.h>
43
#include <sys/socket.h>
44
#include <sys/types.h>
45
#include <sys/stat.h>
46
#include <sys/vfs.h>
47

48
#include <linux/un.h>
49

50
const struct cgroup_library_version library_version = {
51
        .major = CGROUP_VER_MAJOR,
52
        .minor = CGROUP_VER_MINOR,
53
        .release = CGROUP_VER_RELEASE,
54
};
55

56
/*
57
 * The errno which happened the last time (have to be thread specific)
58
 */
59
__thread int last_errno;
60

61
#define MAXLEN 256
62

63
/* the value have to be thread specific */
64
static __thread char errtext[MAXLEN];
65

66
/* Task command name length */
67
#define TASK_COMM_LEN 16
68

69
/* Check if cgroup_init has been called or not. */
70
static int cgroup_initialized;
71

72
/* List of configuration rules */
73
static struct cgroup_rule_list rl;
74

75
/* Temporary list of configuration rules (for non-cache apps) */
76
static struct cgroup_rule_list trl;
77

78
/* Lock for the list of rules (rl) */
79
static pthread_rwlock_t rl_lock = PTHREAD_RWLOCK_INITIALIZER;
80

81
/* Cgroup v2 mount path.  Null if v2 isn't mounted */
82
char cg_cgroup_v2_mount_path[FILENAME_MAX];
83

84
/* Namespace */
85
__thread char *cg_namespace_table[CG_CONTROLLER_MAX];
86

87
pthread_rwlock_t cg_mount_table_lock = PTHREAD_RWLOCK_INITIALIZER;
88
struct cg_mount_table_s cg_mount_table[CG_CONTROLLER_MAX];
89

90
/* Cgroup v2 mount paths, with empty controllers */
91
struct cg_mount_point *cg_cgroup_v2_empty_mount_paths;
92

93
#ifdef WITH_SYSTEMD
94
/* Default systemd path name. Length: <name>.slice/<name>.scope */
95
char systemd_default_cgroup[FILENAME_MAX * 2 + 1];
96
#endif
97

98
const char * const cgroup_strerror_codes[] = {
99
        "Cgroup is not compiled in",
100
        "Cgroup is not mounted",
101
        "Cgroup does not exist",
102
        "Cgroup has not been created",
103
        "Cgroup one of the needed subsystems is not mounted",
104
        "Cgroup, request came in from non owner",
105
        "Cgroup controllers are bound to different mount points",
106
        "Cgroup, operation not allowed",
107
        "Cgroup value set exceeds maximum",
108
        "Cgroup controller already exists",
109
        "Cgroup value already exists",
110
        "Cgroup invalid operation",
111
        "Cgroup, creation of controller failed",
112
        "Cgroup operation failed",
113
        "Cgroup not initialized",
114
        "Cgroup, requested group parameter does not exist",
115
        "Cgroup generic error",
116
        "Cgroup values are not equal",
117
        "Cgroup controllers are different",
118
        "Cgroup parsing failed",
119
        "Cgroup, rules file does not exist",
120
        "Cgroup mounting failed",
121
        "",                                /* 50022 is reserved for future errors */
122
        "End of File or iterator",
123
        "Failed to parse config file",
124
        "Have multiple paths for the same namespace",
125
        "Controller in namespace does not exist",
126
        "Either mount or namespace keyword has to be specified in the configuration file",
127
        "This kernel does not support this feature",
128
        "Value setting does not succeed",
129
        "Failed to remove a non-empty group",
130
        "Failed to convert from cgroup v1 to/from cgroup v2",
131
};
132

133
static const char * const cgroup_ignored_tasks_files[] = { "tasks", NULL };
134

135
#ifndef UNIT_TEST
136
static int cg_get_cgroups_from_proc_cgroups(pid_t pid, char *cgrp_list[],
137
                                            char *controller_list[], int list_len);
138

139
static int cgroupv2_get_subtree_control(const char *path, const char *ctrl_name,
140
                                        bool * const enabled);
141
#endif
142

143
static int cg_chown(const char *filename, uid_t owner, gid_t group)
14,070✔
144
{
145
        if (owner == NO_UID_GID)
14,070✔
146
                owner = getuid();
13,359✔
147
        if (group == NO_UID_GID)
14,070✔
148
                group = getgid();
13,359✔
149

150
        return chown(filename, owner, group);
14,070✔
151
}
152
static int cg_chown_file(FTS *fts, FTSENT *ent, uid_t owner, gid_t group)
14,069✔
153
{
154
        const char *filename = fts->fts_path;
14,069✔
155
        int ret = 0;
14,069✔
156

157
        cgroup_dbg("chown: seeing file %s\n", filename);
14,069✔
158
        switch (ent->fts_info) {
14,069✔
159
        case FTS_ERR:
×
160
                errno = ent->fts_errno;
×
161
                break;
×
162
        case FTS_D:
14,069✔
163
        case FTS_DC:
164
        case FTS_NSOK:
165
        case FTS_NS:
166
        case FTS_DNR:
167
        case FTS_DP:
168
        case FTS_F:
169
        case FTS_DEFAULT:
170
                ret = cg_chown(filename, owner, group);
14,069✔
171
                break;
14,069✔
172
        }
173
        if (ret < 0) {
14,069✔
174
                cgroup_warn("cannot change owner of file %s: %s\n", filename, strerror(errno));
×
175
                last_errno = errno;
×
176
                ret = ECGOTHER;
×
177
        }
178
        return ret;
14,069✔
179
}
180

181
/* TODO: Need to decide a better place to put this function. */
182
static int cg_chown_recursive(char **path, uid_t owner, gid_t group)
237✔
183
{
184
        int ret = 0;
237✔
185
        FTS *fts;
186

187
        cgroup_dbg("chown: path is %s\n", *path);
237✔
188
        fts = fts_open(path, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
237✔
189
        if (fts == NULL) {
237✔
190
                cgroup_warn("cannot open directory %s: %s\n", path, strerror(errno));
×
191
                last_errno = errno;
×
192
                return ECGOTHER;
×
193
        }
194

195
        while (1) {
14,069✔
196
                FTSENT *ent;
197

198
                ent = fts_read(fts);
14,306✔
199
                if (!ent) {
14,306✔
200
                        cgroup_warn("fts_read failed\n");
237✔
201
                        break;
237✔
202
                }
203
                ret = cg_chown_file(fts, ent, owner, group);
14,069✔
204
        }
205
        fts_close(fts);
237✔
206

207
        return ret;
237✔
208
}
209

210
int cg_chmod_path(const char *path, mode_t mode, int owner_is_umask)
389✔
211
{
212
        mode_t mask = -1U;
389✔
213
        struct stat buf;
214
        int fd;
215

216
        fd = open(path, O_RDONLY);
389✔
217
        if (fd == -1)
389✔
218
                goto fail;
×
219

220
        if (owner_is_umask) {
389✔
221
                mode_t umask, gmask, omask;
222
                /*
223
                 * Use owner permissions as an umask for group and others
224
                 * permissions because we trust kernel to initialize owner
225
                 * permissions to something useful.  Keep SUID and SGID bits.
226
                 */
227
                if (fstat(fd, &buf) == -1)
389✔
228
                        goto fail;
×
229

230
                /* 0700 == S_IRWXU */
231
                umask = 0700 & buf.st_mode;
389✔
232
                gmask = umask >> 3;
389✔
233
                omask = gmask >> 3;
389✔
234

235
                mask = umask|gmask|omask|S_ISUID|S_ISGID|S_ISVTX;
389✔
236
        }
237

238
        if (fchmod(fd, mode & mask))
389✔
239
                goto fail;
×
240

241
        close(fd);
389✔
242

243
        return 0;
389✔
244

245
fail:
×
246
        cgroup_warn("cannot change permissions of file %s: %s\n", path, strerror(errno));
×
247
        last_errno = errno;
×
248

249
        if (fd > -1)
×
250
                close(fd);
×
251

252
        return ECGOTHER;
×
253
}
254

255
int cg_chmod_file(FTS *fts, FTSENT *ent, mode_t dir_mode, int dirm_change, mode_t file_mode,
14,069✔
256
                  int filem_change, int owner_is_umask)
257
{
258
        const char *filename = fts->fts_path;
14,069✔
259
        int ret = 0;
14,069✔
260

261
        cgroup_dbg("chmod: seeing file %s\n", filename);
14,069✔
262

263
        switch (ent->fts_info) {
14,069✔
264
        case FTS_ERR:
×
265
                errno = ent->fts_errno;
×
266
                break;
×
267
        case FTS_D:
474✔
268
        case FTS_DC:
269
        case FTS_DNR:
270
        case FTS_DP:
271
                if (dirm_change)
474✔
272
                        ret = cg_chmod_path(filename, dir_mode, owner_is_umask);
12✔
273
                break;
474✔
274
        case FTS_F:
13,595✔
275
        case FTS_NSOK:
276
        case FTS_NS:
277
        case FTS_DEFAULT:
278
                if (filem_change)
13,595✔
279
                        ret = cg_chmod_path(filename, file_mode, owner_is_umask);
376✔
280
                break;
13,595✔
281
        }
282

283
        return ret;
14,069✔
284
}
285

286
/**
287
 * Changes permissions of all directories and control files (i.e. all files
288
 * except files named in ignore_list. The list must be terminated with NULL.
289
 */
290
static int cg_chmod_recursive_controller(char *path, mode_t dir_mode, int dirm_change,
237✔
291
                                         mode_t file_mode, int filem_change, int owner_is_umask,
292
                                         const char * const *ignore_list)
293
{
294
        int final_ret = 0;
237✔
295
        char *fts_path[2];
296
        int i, ignored;
297
        int ret = 0;
237✔
298
        FTS *fts;
299

300
        fts_path[0] = path;
237✔
301
        fts_path[1] = NULL;
237✔
302
        cgroup_dbg("chmod: path is %s\n", path);
237✔
303

304
        fts = fts_open(fts_path, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
237✔
305
        if (fts == NULL) {
237✔
306
                cgroup_warn("cannot open directory %s: %s\n", fts_path, strerror(errno));
×
307
                last_errno = errno;
×
308
                return ECGOTHER;
×
309
        }
310

311
        while (1) {
14,069✔
312
                FTSENT *ent;
313

314
                ent = fts_read(fts);
14,306✔
315
                if (!ent) {
14,306✔
316
                        if (errno != 0) {
237✔
317
                                cgroup_dbg("fts_read failed\n");
×
318
                                last_errno = errno;
×
319
                                final_ret = ECGOTHER;
×
320
                        }
321
                        break;
237✔
322
                }
323

324
                ignored = 0;
14,069✔
325
                if (ignore_list != NULL)
14,069✔
326
                        for (i = 0; ignore_list[i] != NULL; i++)
28,138✔
327
                                if (!strcmp(ignore_list[i], ent->fts_name)) {
14,069✔
328
                                        ignored = 1;
×
329
                                        break;
×
330
                                }
331
                if (ignored)
14,069✔
332
                        continue;
×
333

334
                ret = cg_chmod_file(fts, ent, dir_mode, dirm_change, file_mode, filem_change,
14,069✔
335
                                    owner_is_umask);
336
                if (ret) {
14,069✔
337
                        cgroup_warn("cannot change file mode %s: %s\n", fts_path, strerror(errno));
×
338
                        last_errno = errno;
×
339
                        final_ret = ECGOTHER;
×
340
                }
341
        }
342
        fts_close(fts);
237✔
343

344
        return final_ret;
237✔
345
}
346

347
int cg_chmod_recursive(struct cgroup *cgrp, mode_t dir_mode, int dirm_change, mode_t file_mode,
×
348
                       int filem_change)
349
{
350
        int final_ret = 0;
×
351
        char *path;
352
        int i, ret;
353

354
        path = malloc(FILENAME_MAX);
×
355
        if (!path) {
×
356
                last_errno = errno;
×
357
                return ECGOTHER;
×
358
        }
359
        for (i = 0; i < cgrp->index; i++) {
×
360
                if (!cg_build_path(cgrp->name, path, cgrp->controller[i]->name)) {
×
361
                        final_ret = ECGFAIL;
×
362
                        break;
×
363
                }
364

365
                ret = cg_chmod_recursive_controller(path, dir_mode, dirm_change, file_mode,
×
366
                                                    filem_change, 0, NULL);
367
                if (ret)
×
368
                        final_ret = ret;
×
369
        }
370
        free(path);
×
371

372
        return final_ret;
×
373
}
374

375
void cgroup_set_permissions(struct cgroup *cgrp, mode_t control_dperm, mode_t control_fperm,
15✔
376
                            mode_t task_fperm)
377
{
378
        if (!cgrp) {
15✔
379
                /* ECGROUPNOTALLOWED */
380
                cgroup_err("Cgroup, operation not allowed\n");
1✔
381
                return;
1✔
382
        }
383

384
        cgrp->control_dperm = control_dperm;
14✔
385
        cgrp->control_fperm = control_fperm;
14✔
386
        cgrp->task_fperm = task_fperm;
14✔
387
}
388

389
static char *cgroup_basename(const char *path)
418✔
390
{
391
        char *tmp_string;
392
        char *base;
393

394
        tmp_string = strdup(path);
418✔
395

396
        if (!tmp_string)
418✔
397
                return NULL;
×
398

399
        base = strdup(basename(tmp_string));
418✔
400

401
        free(tmp_string);
418✔
402

403
        return base;
418✔
404
}
405

406
int cgroup_test_subsys_mounted(const char *name)
856✔
407
{
408
        int i;
409

410
        pthread_rwlock_rdlock(&cg_mount_table_lock);
856✔
411

412
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
2,232✔
413
                if (strncmp(cg_mount_table[i].name, name, sizeof(cg_mount_table[i].name)) == 0) {
2,232✔
414
                        pthread_rwlock_unlock(&cg_mount_table_lock);
789✔
415
                        return 1;
789✔
416
                }
417

418
                /*
419
                 * The user has likely requested a file like cgroup.type or
420
                 * cgroup.procs. Allow this request as long as there's a
421
                 * cgroup v2 controller mounted.
422
                 */
423
                if (strncmp(name, CGRP_FILE_PREFIX, strlen(CGRP_FILE_PREFIX)) == 0 &&
1,443✔
424
                    cg_mount_table[i].version == CGROUP_V2) {
67✔
425
                        pthread_rwlock_unlock(&cg_mount_table_lock);
67✔
426
                        return 1;
67✔
427
                }
428
        }
429

430
        pthread_rwlock_unlock(&cg_mount_table_lock);
×
431

432
        return 0;
×
433
}
434

435
/**
436
 * Free a single cgroup_rule struct.
437
 *        @param r The rule to free from memory
438
 */
439
static void cgroup_free_rule(struct cgroup_rule *r)
×
440
{
441
        /* Loop variable */
442
        int i = 0;
×
443

444
        /* Make sure our rule is not NULL, first. */
445
        if (!r) {
×
446
                cgroup_warn("attempted to free NULL rule\n");
×
447
                return;
×
448
        }
449
        if (r->procname) {
×
450
                free(r->procname);
×
451
                r->procname = NULL;
×
452
        }
453
        /* We must free any used controller strings, too. */
454
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
×
455
                if (r->controllers[i])
×
456
                        free(r->controllers[i]);
×
457
        }
458

459
        free(r);
×
460
}
461

462
/**
463
 * Free a list of cgroup_rule structs.  If rl is the main list of rules, the
464
 * lock must be taken for writing before calling this function!
465
 *        @param rl Pointer to the list of rules to free from memory
466
 */
467
static void cgroup_free_rule_list(struct cgroup_rule_list *cg_rl)
×
468
{
469
        /* Temporary pointer */
470
        struct cgroup_rule *tmp = NULL;
×
471

472
        /* Make sure we're not freeing NULL memory! */
473
        if (!(cg_rl->head)) {
×
474
                cgroup_warn("attempted to free NULL list\n");
×
475
                return;
×
476
        }
477

478
        while (cg_rl->head) {
×
479
                tmp = cg_rl->head;
×
480
                cg_rl->head = tmp->next;
×
481
                cgroup_free_rule(tmp);
×
482
        }
483

484
        /* Don't leave wild pointers around! */
485
        cg_rl->head = NULL;
×
486
        cg_rl->tail = NULL;
×
487
}
488

489
static char *cg_skip_unused_charactors_in_rule(char *rule)
2✔
490
{
491
        char *itr;
492

493
        /* We ignore anything after a # sign as comments. */
494
        itr = strchr(rule, '#');
2✔
495
        if (itr)
2✔
496
                *itr = '\0';
×
497

498
        /* We also need to remove the newline character. */
499
        itr = strchr(rule, '\n');
2✔
500
        if (itr)
2✔
501
                *itr = '\0';
2✔
502

503
        /* Now, skip any leading tabs and spaces. */
504
        itr = rule;
2✔
505
        while (itr && isblank(*itr))
2✔
506
                itr++;
×
507

508
        /* If there's nothing left, we can ignore this line. */
509
        if (!strlen(itr))
2✔
510
                return NULL;
×
511

512
        return itr;
2✔
513
}
514

515
/**
516
 * Parse the options field in the rule from the cgrules configuration file
517
 *
518
 *        @param options Comma-separated string of options
519
 *        @param rule The rule that will contain the parsed options
520
 *        @return 0 on success, -EINVAL if the options are invalid
521
 * TODO: Make this function thread safe!
522
 */
523
STATIC int cgroup_parse_rules_options(char *options, struct cgroup_rule * const rule)
6✔
524
{
525
        char *stok_buff = NULL;
6✔
526
        size_t cmp_len;
527
        int ret = 0;
6✔
528

529
        stok_buff = strtok(options, ",");
6✔
530
        if (!stok_buff) {
6✔
531
                cgroup_err("failed to parse options: %s\n", options);
2✔
532
                return -EINVAL;
2✔
533
        }
534

535
        do {
536
                cmp_len = min(strlen(stok_buff), strlen(CGRULE_OPTION_IGNORE));
5✔
537
                if (strlen(stok_buff) == strlen(CGRULE_OPTION_IGNORE) &&
5✔
538
                    strncmp(stok_buff, CGRULE_OPTION_IGNORE, cmp_len) == 0) {
4✔
539
                        rule->is_ignore |= CGRULE_OPT_IGNORE;
3✔
540
                        continue;
3✔
541
                }
542

543
                cmp_len = min(strlen(stok_buff), strlen(CGRULE_OPTION_IGNORE_RT));
2✔
544
                if (strlen(stok_buff) == strlen(CGRULE_OPTION_IGNORE_RT) &&
2✔
545
                    strncmp(stok_buff, CGRULE_OPTION_IGNORE_RT, cmp_len) == 0) {
×
546
                        rule->is_ignore |= CGRULE_OPT_IGNORE_RT;
×
547
                        continue;
×
548
                }
549

550
                cgroup_err("Unsupported option: %s\n", stok_buff);
2✔
551
                ret = -EINVAL;
2✔
552
                break;
2✔
553
        } while ((stok_buff = strtok(NULL, ",")));
3✔
554

555
        return ret;
4✔
556
}
557

558
STATIC int get_next_rule_field(char *rule, char *field, size_t field_len, bool expect_quotes)
34✔
559
{
560
        char *quote_start = NULL, *quote_end = NULL;
34✔
561
        char *tmp, *_rule = rule;
34✔
562
        int len = 0;
34✔
563

564
        if (!rule || !field)
34✔
565
                return ECGINVAL;
2✔
566

567
        /* trim the leading whitespace */
568
        while (*rule == ' ' || *rule == '\t')
54✔
569
                rule++;
22✔
570

571
        tmp = rule;
32✔
572

573
        while (*rule != ' ' && *rule != '\t' && *rule != '\n' && *rule != '\0') {
275✔
574
                if (*rule == '"') {
252✔
575
                        quote_start = rule;
9✔
576
                        rule++;
9✔
577
                        break;
9✔
578
                }
579
                rule++;
243✔
580
        }
581

582
        if (quote_start) {
32✔
583
                if (!expect_quotes)
9✔
584
                        return ECGINVAL;
1✔
585

586
                while (*rule != '"' && *rule != '\n' && *rule != '\0')
144✔
587
                        rule++;
136✔
588

589
                /* there should be a ending quote */
590
                if (*rule != '"')
8✔
591
                        return ECGINVAL;
1✔
592

593
                quote_end = rule;
7✔
594
        }
595

596
        if (quote_end) {
30✔
597
                /* copy until the ending quotes */
598
                len = quote_end - quote_start - 1;
7✔
599
                if (len >= field_len)
7✔
600
                        return ECGINVAL;
2✔
601

602
                strncpy(field, quote_start + 1, len);
5✔
603
                field[len] = '\0';
5✔
604
        } else {
605
                len = rule - tmp;
23✔
606
                if (len >= field_len)
23✔
607
                        return ECGINVAL;
1✔
608

609
                strncpy(field, tmp, len);
22✔
610
                field[len] = '\0';
22✔
611
        }
612

613
        len = (rule - _rule);
27✔
614

615
        /* count until the ending quotes */
616
        if (quote_start)
27✔
617
                len += 1;
5✔
618

619
        return len;
27✔
620
}
621

622
/**
623
 * Parse the configuration file that maps UID/GIDs to cgroups.  If ever the
624
 * configuration file is modified, applications should call this function to
625
 * load the new configuration rules.
626
 *
627
 * The function caller is responsible for calling free() on each rule in the
628
 * list.
629
 *
630
 * The cache parameter alters the behavior of this function.  If true, this
631
 * function will read the entire configuration file and store the results in
632
 * rl (global rules list).  If false, this function will only parse until it
633
 * finds a rule matching the given UID or GID.  It will store this rule in
634
 * trl, as well as any children rules (rules that begin with a %) that it has.
635
 *
636
 * This function is NOT thread safe!
637
 *        @param filename configuration file to parse
638
 *        @param cache True to cache rules, else false
639
 *        @param muid If cache is false, the UID to match against
640
 *        @param mgid If cache is false, the GID to match against
641
 *        @return 0 on success, -1 if no cache and match found, > 0 on error.
642
 * TODO: Make this function thread safe!
643
 *
644
 */
645
static int cgroup_parse_rules_file(char *filename, bool cache, uid_t muid, gid_t mgid,
2✔
646
                                   const char *mprocname)
647
{
648
        /* File descriptor for the configuration file */
649
        FILE *fp = NULL;
2✔
650

651
        /* Buffer to store the line we're working on */
652
        char buff[CGRP_RULE_MAXLINE] = { '\0' };
2✔
653

654
        /* Iterator for the line we're working on */
655
        char *itr = NULL;
2✔
656

657
        /* Pointer to process name in a line of the configuration file */
658
        char *procname = NULL;
2✔
659

660
        /* Pointer to the list that we're using */
661
        struct cgroup_rule_list *lst = NULL;
2✔
662

663
        /* Rule to add to the list */
664
        struct cgroup_rule *newrule = NULL;
2✔
665

666
        /* Structure to get GID from group name */
667
        struct group *grp;
668

669
        /* Structure to get UID from user name */
670
        struct passwd *pwd;
671

672
        /* Temporary storage for a configuration rule */
673
        char key[CGRP_RULE_MAXKEY] = { '\0' };
2✔
674
        char user[LOGIN_NAME_MAX] = { '\0' };
2✔
675
        char controllers[CG_CONTROLLER_MAX] = { '\0' };
2✔
676
        char destination[FILENAME_MAX] = { '\0' };
2✔
677
        char options[CG_OPTIONS_MAX] = { '\0' };
2✔
678
        uid_t uid = CGRULE_INVALID;
2✔
679
        gid_t gid = CGRULE_INVALID;
2✔
680
        bool has_options = false;
2✔
681
        size_t len_username;
682
        int len_procname;
683

684
        /* The current line number */
685
        unsigned int linenum = 0;
2✔
686

687
        /* Did we skip the previous line? */
688
        bool skipped = false;
2✔
689

690
        /* Have we found a matching rule (non-cache mode)? */
691
        bool matched = false;
2✔
692

693
        /* Return codes */
694
        int ret = 0;
2✔
695

696
        /* Temporary buffer for strtok() */
697
        char *stok_buff = NULL;
2✔
698

699
        /* Loop variable. */
700
        int i = 0;
2✔
701

702
        /* Determine which list we're using. */
703
        if (cache)
2✔
704
                lst = &rl;
2✔
705
        else
706
                lst = &trl;
×
707

708
        /* Open the configuration file. */
709
        fp = fopen(filename, "re");
2✔
710
        if (!fp) {
2✔
711
                cgroup_warn("failed to open configuration file %s: %s\n",
×
712
                            filename, strerror(errno));
713
                /* originally ret = 0, but this is parse fail, not success */
714
                ret = ECGRULESPARSEFAIL;
×
715
                goto finish;
×
716
        }
717

718
        /* Now, parse the configuration file one line at a time. */
719
        cgroup_dbg("Parsing configuration file %s.\n", filename);
2✔
720
        while (fgets(buff, sizeof(buff), fp) != NULL) {
4✔
721
                linenum++;
2✔
722

723
                itr = cg_skip_unused_charactors_in_rule(buff);
2✔
724
                if (!itr)
2✔
725
                        continue;
×
726

727
                /*
728
                 * If we skipped the last rule and this rule is a continuation
729
                 * of it (begins with %), then we should skip this rule too.
730
                 */
731
                if (skipped && *itr == '%') {
2✔
732
                        cgroup_warn("skipped child of invalid rule, line %d.\n", linenum);
×
733
                        continue;
×
734
                }
735

736
                /* clear the buffer. */
737
                grp = NULL;
2✔
738
                pwd = NULL;
2✔
739

740
                /*
741
                 * If there is something left, it should be a rule.  Otherwise,
742
                 * there's an error in the configuration file.
743
                 */
744
                skipped = false;
2✔
745

746
                ret = get_next_rule_field(itr, key, CGRP_RULE_MAXKEY, true);
2✔
747
                if (!ret) {
2✔
748
                        cgroup_err("failed to parse configuration file on line %d\n", linenum);
×
749
                        goto parsefail;
×
750
                }
751

752
                itr += ret;
2✔
753
                ret = get_next_rule_field(itr, controllers, CG_CONTROLLER_MAX, false);
2✔
754
                if (!ret) {
2✔
755
                        cgroup_err("failed to parse configuration file on line %d\n", linenum);
×
756
                        goto parsefail;
×
757
                }
758

759
                itr += ret;
2✔
760
                ret = get_next_rule_field(itr, destination, FILENAME_MAX, true);
2✔
761
                if (!ret) {
2✔
762
                        cgroup_err("failed to parse configuration file on line %d\n", linenum);
×
763
                        goto parsefail;
×
764
                }
765

766
                itr += ret;
2✔
767
                ret = get_next_rule_field(itr, options, CG_OPTIONS_MAX, false);
2✔
768
                if (!ret)
2✔
769
                        has_options = false;
2✔
770
                else
771
                        has_options = true;
×
772

773
                procname = strchr(key, ':');
2✔
774
                if (procname) {
2✔
775
                        /* <user>:<procname>  <subsystem>  <destination> */
776
                        procname++;        /* skip ':' */
2✔
777
                        len_username = procname - key - 1;
2✔
778
                        len_procname = strlen(procname);
2✔
779
                        if (len_procname < 0) {
2✔
780
                                cgroup_err("failed to parse configuration file on line %d\n",
×
781
                                           linenum);
782
                                goto parsefail;
×
783
                        }
784
                } else {
785
                        len_username = strlen(key);
×
786
                        len_procname = 0;
×
787
                }
788
                len_username = min(len_username, sizeof(user) - 1);
2✔
789
                memset(user, '\0', sizeof(user));
2✔
790
                strncpy(user, key, len_username);
2✔
791
                user[sizeof(user) - 1] = '\0';
2✔
792

793
                /*
794
                 * Next, check the user/group.  If it's a % sign, then we are
795
                 * continuing another rule and UID/GID should not be reset.
796
                 * If it's a @, we're dealing with a GID rule.  If it's a *,
797
                 * then we do not need to do a lookup because the rule always
798
                 * applies (it's a wildcard).  If we're using non-cache mode
799
                 * and we've found a matching rule, we only continue to parse
800
                 * if we're looking at a child rule.
801
                 */
802
                if ((!cache) && matched && (strncmp(user, "%", 1) != 0)) {
2✔
803
                        /* If we make it here, we finished (non-cache). */
804
                        cgroup_dbg("Parsing of configuration file complete.\n\n");
×
805
                        ret = -1;
×
806
                        goto close;
×
807
                }
808
                if (strncmp(user, "@", 1) == 0) {
2✔
809
                        /* New GID rule. */
810
                        itr = &(user[1]);
×
811
                        grp = getgrnam(itr);
×
812
                        if (grp) {
×
813
                                uid = CGRULE_INVALID;
×
814
                                gid = grp->gr_gid;
×
815
                        } else {
816
                                cgroup_warn("Entry for %s not found. Skipping rule on line %d.\n",
×
817
                                            itr, linenum);
818
                                skipped = true;
×
819
                                continue;
×
820
                        }
821
                } else if (strncmp(user, "*", 1) == 0) {
2✔
822
                        /* Special wildcard rule. */
823
                        uid = CGRULE_WILD;
2✔
824
                        gid = CGRULE_WILD;
2✔
825
                } else if (*itr != '%') {
×
826
                        /* New UID rule. */
827
                        pwd = getpwnam(user);
×
828
                        if (pwd) {
×
829
                                uid = pwd->pw_uid;
×
830
                                gid = CGRULE_INVALID;
×
831
                        } else {
832
                                cgroup_warn("Entry for %s not found. Skipping rule on line %d.\n",
×
833
                                            user, linenum);
834
                                skipped = true;
×
835
                                continue;
×
836
                        }
837
                } /* Else, we're continuing another rule (UID/GID are okay). */
838

839
                /*
840
                 * If we are not caching rules, then we need to check for a
841
                 * match before doing anything else.  We consider four cases:
842
                 * 1. The UID matches
843
                 * 2. The GID matches
844
                 * 3. The UID is a member of the GID, or
845
                 * 4. We're looking at the wildcard rule, which always matches.
846
                 * If none of these are true, we simply continue to the next
847
                 * line in the file.
848
                 */
849
                if (grp && muid != CGRULE_INVALID) {
2✔
850
                        pwd = getpwuid(muid);
×
851
                        if (!pwd)
×
852
                                continue;
×
853

854
                        for (i = 0; grp->gr_mem[i]; i++) {
×
855
                                if (!(strcmp(pwd->pw_name, grp->gr_mem[i])))
×
856
                                        matched = true;
×
857
                        }
858
                }
859

860
                if (uid == muid || gid == mgid || uid == CGRULE_WILD)
2✔
861
                        matched = true;
2✔
862

863
                if (!cache) {
2✔
864
                        if (!matched)
×
865
                                continue;
×
866
                        if (len_procname) {
×
867
                                char *mproc_base;
868
                                /*
869
                                 * If there is a rule based on process name,
870
                                 * it should be matched with mprocname.
871
                                 */
872
                                if (!mprocname) {
×
873
                                        uid = CGRULE_INVALID;
×
874
                                        gid = CGRULE_INVALID;
×
875
                                        matched = false;
×
876
                                        continue;
×
877
                                }
878

879
                                mproc_base = cgroup_basename(mprocname);
×
880
                                if (strcmp(mprocname, procname) && strcmp(mproc_base, procname)) {
×
881
                                        uid = CGRULE_INVALID;
×
882
                                        gid = CGRULE_INVALID;
×
883
                                        matched = false;
×
884
                                        free(mproc_base);
×
885
                                        continue;
×
886
                                }
887
                                free(mproc_base);
×
888
                        }
889
                }
890

891
                /*
892
                 * Now, we're either caching rules or we found a match.
893
                 * Either way, copy everything into a new rule and push it
894
                 * into the list.
895
                 */
896
                newrule = calloc(1, sizeof(struct cgroup_rule));
2✔
897
                if (!newrule) {
2✔
898
                        cgroup_err("out of memory? Error was: %s\n", strerror(errno));
×
899
                        last_errno = errno;
×
900
                        ret = ECGOTHER;
×
901
                        goto close;
×
902
                }
903

904
                newrule->uid = uid;
2✔
905
                newrule->gid = gid;
2✔
906
                newrule->is_ignore = 0;
2✔
907

908
                len_username = min(len_username, sizeof(newrule->username) - 1);
2✔
909
                strncpy(newrule->username, user, len_username);
2✔
910
                newrule->username[sizeof(newrule->username) - 1] = '\0';
2✔
911

912
                if (len_procname) {
2✔
913
                        newrule->procname = strdup(procname);
2✔
914
                        if (!newrule->procname) {
2✔
915
                                cgroup_err("strdup failed to allocate memory %s\n",
×
916
                                           strerror(errno));
917
                                free(newrule);
×
918
                                last_errno = errno;
×
919
                                ret = ECGOTHER;
×
920
                                goto close;
×
921
                        }
922
                } else {
923
                        newrule->procname = NULL;
×
924
                }
925
                strncpy(newrule->destination, destination, sizeof(newrule->destination) - 1);
2✔
926
                newrule->destination[sizeof(newrule->destination) - 1] = '\0';
2✔
927

928
                if (has_options) {
2✔
929
                        ret = cgroup_parse_rules_options(options, newrule);
×
930
                        if (ret < 0)
×
931
                                goto destroyrule;
×
932
                }
933

934
                newrule->next = NULL;
2✔
935

936
                /* Parse the controller list, and add that to newrule too. */
937
                stok_buff = strtok(controllers, ",");
2✔
938
                if (!stok_buff) {
2✔
939
                        cgroup_err("failed to parse controllers on line %d\n", linenum);
×
940
                        goto destroyrule;
×
941
                }
942

943
                i = 0;
2✔
944
                do {
945
                        if (i >= MAX_MNT_ELEMENTS) {
2✔
946
                                cgroup_err("too many controllers listed on line %d\n", linenum);
×
947
                                goto destroyrule;
×
948
                        }
949

950
                        newrule->controllers[i] =
2✔
951
                                strndup(stok_buff, strlen(stok_buff) + 1);
2✔
952
                        if (!(newrule->controllers[i])) {
2✔
953
                                cgroup_err("out of memory? Error was: %s\n", strerror(errno));
×
954
                                goto destroyrule;
×
955
                        }
956
                        i++;
2✔
957
                } while ((stok_buff = strtok(NULL, ",")));
2✔
958

959
                /* Now, push the rule. */
960
                if (lst->head == NULL) {
2✔
961
                        lst->head = newrule;
2✔
962
                        lst->tail = newrule;
2✔
963
                } else {
964
                        lst->tail->next = newrule;
×
965
                        lst->tail = newrule;
×
966
                }
967

968
                cgroup_dbg("Added rule %s (UID: %d, GID: %d) -> %s for controllers:",
2✔
969
                           lst->tail->username, lst->tail->uid, lst->tail->gid,
970
                           lst->tail->destination);
971

972
                for (i = 0; lst->tail->controllers[i]; i++)
4✔
973
                        cgroup_dbg(" %s", lst->tail->controllers[i]);
2✔
974
                cgroup_dbg("\n");
2✔
975
        }
976

977
        /* If we make it here, there were no errors. */
978
        cgroup_dbg("Parsing of configuration file complete.\n\n");
2✔
979
        ret = (matched && !cache) ? -1 : 0;
2✔
980
        goto close;
2✔
981

982
destroyrule:
×
983
        cgroup_free_rule(newrule);
×
984

985
parsefail:
×
986
        ret = ECGRULESPARSEFAIL;
×
987

988
close:
2✔
989
        fclose(fp);
2✔
990
finish:
2✔
991
        return ret;
2✔
992
}
993

994
/**
995
 * Parse CGRULES_CONF_FILE and all files in CGRULES_CONF_FILE_DIR.
996
 * If CGRULES_CONF_FILE_DIR does not exists or can not be read, parse only
997
 * CGRULES_CONF_FILE. This way we keep the back compatibility.
998
 *
999
 * Original description of this function moved to cgroup_parse_rules_file.
1000
 * Also cloned and all occurrences of file changed to files.
1001
 *
1002
 * Parse the configuration files that maps UID/GIDs to cgroups. If ever the
1003
 * configuration files are modified, applications should call this function to
1004
 * load the new configuration rules. The function caller is responsible for
1005
 * calling free() on each rule in the list.
1006
 *
1007
 * The cache parameter alters the behavior of this function.  If true, this
1008
 * function will read the entire content of all configuration files and store
1009
 * the results in rl (global rules list). If false, this function will only
1010
 * parse until it finds a file and a rule matching the given UID or GID.
1011
 * The remaining files are skipped. It will store this rule in trl, as well as
1012
 * any children rules (rules that begin with a %) that it has.
1013
 *
1014
 * Files can be read in an random order so the first match must not be
1015
 * dependent on it. Thus construct the rules the way not to break this
1016
 * assumption.
1017
 *
1018
 * This function is NOT thread safe!
1019
 *        @param cache True to cache rules, else false
1020
 *        @param muid If cache is false, the UID to match against
1021
 *        @param mgid If cache is false, the GID to match against
1022
 *        @return 0 on success, -1 if no cache and match found, > 0 on error.
1023
 * TODO: Make this function thread safe!
1024
 */
1025
static int cgroup_parse_rules(bool cache, uid_t muid, gid_t mgid, const char *mprocname)
2✔
1026
{
1027
        /* Pointer to the list that we're using */
1028
        struct cgroup_rule_list *lst = NULL;
2✔
1029

1030
        /* Directory variables */
1031
        const char *dirname = CGRULES_CONF_DIR;
2✔
1032
        struct dirent *item;
1033
        char *tmp;
1034
        int sret;
1035
        DIR *d;
1036

1037
        int ret;
1038

1039
        /* Determine which list we're using. */
1040
        if (cache)
2✔
1041
                lst = &rl;
2✔
1042
        else
1043
                lst = &trl;
×
1044

1045
        /* If our list already exists, clean it. */
1046
        if (lst->head)
2✔
1047
                cgroup_free_rule_list(lst);
×
1048

1049
        pthread_rwlock_wrlock(&rl_lock);
2✔
1050

1051
        /* Parse CGRULES_CONF_FILE configuration file (back compatibility). */
1052
        ret = cgroup_parse_rules_file(CGRULES_CONF_FILE, cache, muid, mgid, mprocname);
2✔
1053

1054
        /*
1055
         * if match (ret = -1), stop parsing other files,
1056
         * just return or ret > 0 => error
1057
         */
1058
        if (ret != 0) {
2✔
1059
                pthread_rwlock_unlock(&rl_lock);
×
1060
                return ret;
×
1061
        }
1062

1063
        /* Continue parsing */
1064
        d = opendir(dirname);
2✔
1065
        if (!d) {
2✔
1066
                cgroup_warn("Failed to open directory %s: %s\n", dirname, strerror(errno));
2✔
1067
                /*
1068
                 * Cannot read directory. However, CGRULES_CONF_FILE is
1069
                 * successfully parsed. Thus return as a success for back
1070
                 * compatibility.
1071
                 */
1072
                pthread_rwlock_unlock(&rl_lock);
2✔
1073

1074
                return 0;
2✔
1075
        }
1076

1077
        /* Read all files from CGRULES_CONF_FILE_DIR */
1078
        do {
1079
                item = readdir(d);
×
1080
                if (item && (item->d_type == DT_REG || item->d_type == DT_LNK)) {
×
1081

1082
                        sret = asprintf(&tmp, "%s/%s", dirname, item->d_name);
×
1083
                        if (sret < 0) {
×
1084
                                cgroup_err("Out of memory\n");
×
1085

1086
                                /*
1087
                                 * Cannot read directory.
1088
                                 * However, CGRULES_CONF_FILE is successfully
1089
                                 * parsed. Thus return as a success for back
1090
                                 * compatibility.
1091
                                 */
1092
                                ret = 0;
×
1093
                                goto unlock_list;
×
1094
                        }
1095

1096
                        cgroup_dbg("Parsing cgrules file: %s\n", tmp);
×
1097
                        ret = cgroup_parse_rules_file(tmp, cache, muid, mgid, mprocname);
×
1098

1099
                        free(tmp);
×
1100

1101
                        /* Match with cache disabled? */
1102
                        if (ret != 0)
×
1103
                                goto unlock_list;
×
1104
                }
1105
                if (!item && errno) {
×
1106
                        cgroup_warn("cannot read %s: %s\n", dirname, strerror(errno));
×
1107
                        /*
1108
                         * Cannot read an item.
1109
                         * But continue for back compatibility as a success.
1110
                         */
1111
                        ret = 0;
×
1112
                        goto unlock_list;
×
1113
                }
1114
        } while (item != NULL);
×
1115

1116
unlock_list:
×
1117
        closedir(d);
×
1118
        pthread_rwlock_unlock(&rl_lock);
×
1119

1120
        return ret;
×
1121
}
1122

1123
int cg_add_duplicate_mount(struct cg_mount_table_s *item, const char *path)
8✔
1124
{
1125
        struct cg_mount_point *mount, *it;
1126

1127
        mount = malloc(sizeof(struct cg_mount_point));
8✔
1128
        if (!mount) {
8✔
1129
                last_errno = errno;
×
1130
                return ECGOTHER;
×
1131
        }
1132
        mount->next = NULL;
8✔
1133

1134
        strncpy(mount->path, path, sizeof(mount->path));
8✔
1135
        mount->path[sizeof(mount->path)-1] = '\0';
8✔
1136

1137
        /*
1138
         * Add the mount point to the end of the list. Assuming the list is
1139
         * short, no optimization is done.
1140
         */
1141
        it = &item->mount;
8✔
1142
        while (it->next)
8✔
1143
                it = it->next;
×
1144

1145
        it->next = mount;
8✔
1146

1147
        return 0;
8✔
1148
}
1149

1150
/*
1151
 * Tries to find if any controller in cg_mount_table have already mounted on
1152
 * the mount_path and if mounted sets the matching controller idx share_mnt
1153
 * flag and Return 1 or 0 otherwise.
1154
 */
1155
static int cgroup_set_cg_mnt_tbl_shared_mnt(char *mount_path, int *mnt_tbl_idx)
13,349✔
1156
{
1157
        int i, shared_mnt = 0;
13,349✔
1158

1159
        /* Check if controllers share mount points */
1160
        for  (i = 0; i < *mnt_tbl_idx; i++) {
33,055✔
1161
                if (strncmp(mount_path, cg_mount_table[i].mount.path, FILENAME_MAX) == 0) {
31,240✔
1162
                        cg_mount_table[i].shared_mnt = 1;
11,534✔
1163
                        shared_mnt = 1;
11,534✔
1164
                        break;
11,534✔
1165
                }
1166
        }
1167

1168
        return shared_mnt;
13,349✔
1169
}
1170

1171
static void cgroup_cg_mount_table_append(const char *name, const char *mount_path,
13,341✔
1172
                                         enum cg_version_t version, int *mnt_tbl_idx,
1173
                                         const char *mnt_opts, int shared_mnt)
1174
{
1175
        int i = *mnt_tbl_idx;
13,341✔
1176

1177
        strncpy(cg_mount_table[i].name,        name, CONTROL_NAMELEN_MAX);
13,341✔
1178
        cg_mount_table[i].name[CONTROL_NAMELEN_MAX-1] = '\0';
13,341✔
1179

1180
        strncpy(cg_mount_table[i].mount.path, mount_path, FILENAME_MAX);
13,341✔
1181
        cg_mount_table[i].mount.path[FILENAME_MAX-1] = '\0';
13,341✔
1182

1183
        cg_mount_table[i].shared_mnt = shared_mnt;
13,341✔
1184
        cg_mount_table[i].version = version;
13,341✔
1185
        cg_mount_table[i].mount.next = NULL;
13,341✔
1186

1187
        cgroup_dbg("Found cgroup option %s, count %d\n", mnt_opts, i);
13,341✔
1188

1189
        (*mnt_tbl_idx)++;
13,341✔
1190
}
13,341✔
1191

1192
/**
1193
 * Process a cgroup v1 mount and add it to cg_mount_table if it's not a
1194
 * duplicate.
1195
 *
1196
 *        @param controllers List of controllers from /proc/cgroups
1197
 *        @param ent File system description of cgroup mount being processed
1198
 *        @param mnt_tbl_idx cg_mount_table index
1199
 */
1200
STATIC int cgroup_process_v1_mnt(char *controllers[], struct mntent *ent, int *mnt_tbl_idx)
367✔
1201
{
1202
        char *strtok_buffer = NULL, *mntopt = NULL;
367✔
1203
        int shared_mnt, duplicate;
1204
        int i, j, ret = 0;
367✔
1205
        char c = 0;
367✔
1206

1207
        for (i = 0; controllers[i] != NULL; i++) {
5,471✔
1208
                mntopt = hasmntopt(ent, controllers[i]);
5,104✔
1209

1210
                if (!mntopt)
5,104✔
1211
                        continue;
5,102✔
1212

1213
                c = mntopt[strlen(controllers[i])];
2✔
1214

1215
                if (c != '\0' && c != ',')
2✔
1216
                        continue;
×
1217

1218
                cgroup_dbg("found %s in %s\n", controllers[i], ent->mnt_opts);
2✔
1219

1220
                /* Check if controllers share mount points */
1221
                shared_mnt = cgroup_set_cg_mnt_tbl_shared_mnt(ent->mnt_dir, mnt_tbl_idx);
2✔
1222

1223
                /* Do not have duplicates in mount table */
1224
                duplicate = 0;
2✔
1225
                for  (j = 0; j < *mnt_tbl_idx; j++) {
2✔
1226
                        if (strncmp(controllers[i], cg_mount_table[j].name, FILENAME_MAX) == 0) {
1✔
1227
                                duplicate = 1;
1✔
1228
                                break;
1✔
1229
                        }
1230
                }
1231

1232
                if (duplicate) {
2✔
1233
                        cgroup_dbg("controller %s is already mounted on %s\n", mntopt,
1✔
1234
                                   cg_mount_table[j].mount.path);
1235
                        ret = cg_add_duplicate_mount(&cg_mount_table[j], ent->mnt_dir);
1✔
1236
                        if (ret)
1✔
1237
                                goto out;
×
1238
                        /* Continue with next controller */
1239
                        continue;
1✔
1240
                }
1241

1242
                cgroup_cg_mount_table_append(controllers[i], ent->mnt_dir, CGROUP_V1, mnt_tbl_idx,
1✔
1243
                                             ent->mnt_opts, shared_mnt);
1✔
1244

1245
                if ((*mnt_tbl_idx) >= CG_CONTROLLER_MAX)
1✔
1246
                        goto out;
×
1247
        }
1248

1249
        /* Doesn't match the controller. Check if it is a named hierarchy. */
1250
        mntopt = hasmntopt(ent, "name");
367✔
1251

1252
        if (mntopt) {
367✔
1253
                mntopt = strtok_r(mntopt, ",", &strtok_buffer);
365✔
1254
                if (!mntopt)
365✔
1255
                        goto out;
×
1256

1257
#ifdef OPAQUE_HIERARCHY
1258
                /* Ignore the opaque hierarchy. */
1259
                if (strcmp(mntopt, OPAQUE_HIERARCHY) == 0)
365✔
1260
                        goto out;
1✔
1261
#endif
1262
                /* Check if controllers share mount points */
1263
                shared_mnt = cgroup_set_cg_mnt_tbl_shared_mnt(ent->mnt_dir, mnt_tbl_idx);
364✔
1264

1265
                /* Check if it is a duplicate */
1266
                duplicate = 0;
364✔
1267
                for (j = 0; j < *mnt_tbl_idx; j++) {
20,020✔
1268
                        if (strncmp(mntopt, cg_mount_table[j].name, FILENAME_MAX) == 0) {
19,656✔
1269
                                duplicate = 1;
×
1270
                                break;
×
1271
                        }
1272
                }
1273

1274
                if (duplicate) {
364✔
1275
                        cgroup_dbg("controller %s is already mounted on %s\n", mntopt,
×
1276
                                   cg_mount_table[j].mount.path);
1277
                        ret = cg_add_duplicate_mount(&cg_mount_table[j], ent->mnt_dir);
×
1278
                        goto out;
×
1279
                }
1280

1281
                cgroup_cg_mount_table_append(mntopt, ent->mnt_dir, CGROUP_V1, mnt_tbl_idx,
364✔
1282
                                             ent->mnt_opts, shared_mnt);
364✔
1283
        }
1284

1285
out:
2✔
1286
        return ret;
367✔
1287
}
1288

1289
/**
1290
 * Process a cgroup v2 mount and add it to cg_mount_table if it's not a
1291
 * duplicate.
1292
 *
1293
 *        @param ent File system description of cgroup mount being processed
1294
 *        @param mnt_tbl_idx cg_mount_table index
1295
 */
1296
STATIC int cgroup_process_v2_mnt(struct mntent *ent, int *mnt_tbl_idx)
1,444✔
1297
{
1298
        char *ret_c = NULL, line[CGV2_CONTROLLERS_LL_MAX], *stok_buff = NULL;
1,444✔
1299
        char *controller = NULL, *controllers = NULL;
1,444✔
1300
        char cgrp_controllers_path[FILENAME_MAX];
1301
        int ret = 0, i, duplicate, shared_mnt;
1,444✔
1302
        FILE *fp = NULL;
1,444✔
1303

1304
        /*
1305
         * Save off this mount point.  This may be used later to
1306
         * build the cg_path.
1307
         */
1308
        strncpy(cg_cgroup_v2_mount_path, ent->mnt_dir, FILENAME_MAX-1);
1,444✔
1309
        cg_cgroup_v2_mount_path[FILENAME_MAX-1] = '\0';
1,444✔
1310

1311
        /* determine what v2 controllers are available on this mount */
1312
        snprintf(cgrp_controllers_path, FILENAME_MAX, "%s/%s", ent->mnt_dir, CGV2_CONTROLLERS_FILE);
1,444✔
1313
        fp = fopen(cgrp_controllers_path, "re");
1,444✔
1314
        if (!fp) {
1,444✔
1315
                ret = ECGOTHER;
×
1316
                goto out;
×
1317
        }
1318

1319
        ret_c = fgets(line, CGV2_CONTROLLERS_LL_MAX, fp);
1,444✔
1320
        if (ret_c == NULL) {
1,444✔
1321
                struct cg_mount_point *tmp, *t;
1322

1323
                ret = ECGEOF;
1✔
1324

1325
                tmp = malloc(sizeof(struct cg_mount_point));
1✔
1326
                if (tmp == NULL) {
1✔
1327
                        last_errno = errno;
×
1328
                        ret = ECGOTHER;
×
1329
                        goto out;
×
1330
                }
1331

1332
                strncpy(tmp->path, cg_cgroup_v2_mount_path, sizeof(tmp->path) - 1);
1✔
1333
                tmp->path[sizeof(tmp->path)-1] = '\0';
1✔
1334
                tmp->next = NULL;
1✔
1335

1336
                t = cg_cgroup_v2_empty_mount_paths;
1✔
1337
                if (t == NULL) {
1✔
1338
                        cg_cgroup_v2_empty_mount_paths = tmp;
1✔
1339
                        goto out;
1✔
1340
                }
1341

1342
                while (t->next != NULL)
×
1343
                        t = t->next;
×
1344
                t->next = tmp;
×
1345

1346
                goto out;
×
1347
        }
1348

1349
        /* Remove the trailing newline */
1350
        ret_c[strlen(ret_c) - 1] = '\0';
1,443✔
1351

1352
        /*
1353
         * The "cgroup" controller is a pseudo-controller that has settings that a user may
1354
         * wish to read/modify.  Add it to our cg_mount_table so that it can be manipulated
1355
         * like other "normal" controllers
1356
         */
1357
        controllers = malloc(strlen(ret_c) + strlen(CGRP_FILE_PREFIX) + 2);
1,443✔
1358
        if (!controllers) {
1,443✔
1359
                ret = ECGOTHER;
×
1360
                goto out;
×
1361
        }
1362

1363
        sprintf(controllers, "%s %s", ret_c, CGRP_FILE_PREFIX);
1,443✔
1364

1365
        /*
1366
         * cgroup.controllers returns a list of available controllers in
1367
         * the following format:
1368
         *        cpuset cpu io memory pids rdma
1369
         */
1370
        controller = strtok_r(controllers, " ", &stok_buff);
1,443✔
1371
        do {
1372
                /* Check if controllers share mount points */
1373
                shared_mnt = cgroup_set_cg_mnt_tbl_shared_mnt(ent->mnt_dir, mnt_tbl_idx);
12,983✔
1374

1375
                /* Do not have duplicates in mount table */
1376
                duplicate = 0;
12,983✔
1377
                for  (i = 0; i < *mnt_tbl_idx; i++) {
64,901✔
1378
                        if (strncmp(cg_mount_table[i].name, controller, FILENAME_MAX) == 0) {
51,925✔
1379
                                duplicate = 1;
7✔
1380
                                break;
7✔
1381
                        }
1382
                }
1383

1384
                if (duplicate) {
12,983✔
1385
                        cgroup_dbg("controller %s is already mounted on %s\n", controller,
7✔
1386
                                   cg_mount_table[i].mount.path);
1387

1388
                        ret = cg_add_duplicate_mount(&cg_mount_table[i], ent->mnt_dir);
7✔
1389
                        if (ret)
7✔
1390
                                break;
×
1391

1392
                        continue;
7✔
1393
                }
1394

1395
                /* This controller is not in the mount table.  Add it */
1396
                cgroup_cg_mount_table_append(controller, ent->mnt_dir, CGROUP_V2, mnt_tbl_idx,
12,976✔
1397
                                             controller, shared_mnt);
1398

1399
                if ((*mnt_tbl_idx) >= CG_CONTROLLER_MAX)
12,976✔
1400
                        goto out;
×
1401
        } while ((controller = strtok_r(NULL, " ", &stok_buff)));
12,983✔
1402

1403
out:
1,443✔
1404
        if (fp)
1,444✔
1405
                fclose(fp);
1,444✔
1406

1407
        if (controllers)
1,444✔
1408
                free(controllers);
1,443✔
1409

1410
        return ret;
1,444✔
1411
}
1412

1413
/*
1414
 * Free global variables filled by previous cgroup_init(). This function
1415
 * should be called with cg_mount_table_lock taken.
1416
 */
1417
static void cgroup_free_cg_mount_table(void)
1,441✔
1418
{
1419
        struct cg_mount_point *mount, *tmp;
1420
        int i;
1421

1422
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
5,836✔
1423
                mount = cg_mount_table[i].mount.next;
4,395✔
1424

1425
                while (mount) {
4,395✔
1426
                        tmp = mount;
×
1427
                        mount = mount->next;
×
1428
                        free(tmp);
×
1429
                }
1430
        }
1431

1432
        memset(&cg_mount_table, 0, sizeof(cg_mount_table));
1,441✔
1433
        memset(&cg_cgroup_v2_mount_path, 0, sizeof(cg_cgroup_v2_mount_path));
1,441✔
1434
        memset(&cg_cgroup_v2_empty_mount_paths, 0, sizeof(cg_cgroup_v2_empty_mount_paths));
642✔
1435
}
1,441✔
1436

1437
/*
1438
 * Parses the mount options of the given mount point and checks for the
1439
 * option in the list of mount options and sets is_set accordingly.
1440
 * @mnt: Mount point name to search for mount points.
1441
 * @mnt_opt: Mount option to be searched.
1442
 * @is_set: Set to 1, when mount option is found, 0 otherwise.
1443
 *
1444
 * Returns 0, in case of success and ECGOTHER on failure.
1445
 */
1446
static int check_mount_point_opt(const char *mnt, const char *mnt_opt, int * const is_set)
×
1447
{
1448
        struct mntent *ent, *temp_ent = NULL;
×
1449
        char mntent_buffer[4 * FILENAME_MAX];
1450
        char *mntopt = NULL;
×
1451
        char mnt_opt_delim;
1452
        FILE *proc_mount;
1453
        int ret = 0;
×
1454

1455
        if (!mnt || !mnt_opt || !is_set)
×
1456
                return ECGINVAL;
×
1457

1458
        proc_mount = setmntent(mnt, "r");
×
1459
        if (!proc_mount) {
×
1460
                cgroup_err("cannot open %s: %s\n", mnt, strerror(errno));
×
1461
                last_errno = errno;
×
1462
                ret = ECGOTHER;
×
1463
                goto err;
×
1464
        }
1465

1466
        temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
×
1467
        if (!temp_ent) {
×
1468
                last_errno = errno;
×
1469
                ret = ECGOTHER;
×
1470
                goto err;
×
1471
        }
1472

1473
        ent = getmntent_r(proc_mount, temp_ent,        mntent_buffer, sizeof(mntent_buffer));
×
1474
        if (!ent) {
×
1475
                last_errno = errno;
×
1476
                ret = ECGOTHER;
×
1477
                goto err;
×
1478
        }
1479

1480
        *is_set = 0;
×
1481
        while ((mntopt = hasmntopt(ent, mnt_opt))) {
×
1482
                mnt_opt_delim = mntopt[strlen(mnt_opt)];
×
1483
                if (mnt_opt_delim == '\0' || mnt_opt_delim == ',') {
×
1484
                        *is_set  = 1;
×
1485
                        break;
×
1486
                }
1487
        }
1488

1489
        if (*is_set == 0)
×
1490
                cgroup_dbg("%s, not found in the list of mount options of %s\n", mnt_opt, mnt);
×
1491

1492
err:
×
1493
        if (proc_mount)
×
1494
                endmntent(proc_mount);
×
1495

1496
        if (temp_ent)
×
1497
                free(temp_ent);
×
1498

1499
        return ret;
×
1500
}
1501

1502
/*
1503
 * Reads /proc/cgroups and populates the controllers/subsys_name. This
1504
 * function should be called with cg_mount_table_lock taken.
1505
 */
1506
static int cgroup_populate_controllers(char *controllers[CG_CONTROLLER_MAX])
1,441✔
1507
{
1508
        int hierarchy, num_cgrps, enabled;
1509
        char subsys_name[FILENAME_MAX];
1510
        char mnt_opt[] = "subset=pid";
1,441✔
1511
        char *buf = NULL;
1,441✔
1512
        FILE *proc_cgrp;
1513
        int mnt_opt_set;
1514
        int ret = 0;
1,441✔
1515
        int err, i = 0;
1,441✔
1516

1517
        proc_cgrp = fopen("/proc/cgroups", "re");
1,441✔
1518
        if (!proc_cgrp) {
1,441✔
1519
                cgroup_warn("cannot open /proc/cgroups: %s\n", strerror(errno));
×
1520
                ret = check_mount_point_opt("/proc/self/mounts", mnt_opt, &mnt_opt_set);
×
1521
                if (ret)
×
1522
                        goto err;
×
1523

1524
                if (!mnt_opt_set)
×
1525
                        ret = ECGINVAL;
×
1526
                /*
1527
                 * /proc, mounted with subset=pid is valid. cgroup v2 doesn't
1528
                 * depend on /proc/cgroups to parse the available controllers.
1529
                 */
1530
                goto err;
×
1531
        }
1532

1533
        /*
1534
         * The first line of the file has stuff we are not interested in.
1535
         * So just read it and discard the information.
1536
         */
1537
        buf = malloc(CGV2_CONTROLLERS_LL_MAX);
1,441✔
1538
        if (!buf) {
1,441✔
1539
                last_errno = errno;
×
1540
                ret = ECGOTHER;
×
1541
                goto err;
×
1542
        }
1543

1544
        if (!fgets(buf, CGV2_CONTROLLERS_LL_MAX, proc_cgrp)) {
1,441✔
1545
                cgroup_err("cannot read /proc/cgroups: %s\n", strerror(errno));
×
1546
                last_errno = errno;
×
1547
                ret = ECGOTHER;
×
1548
                goto err;
×
1549
        }
1550

1551
        while (!feof(proc_cgrp)) {
21,615✔
1552
                /*
1553
                 * check Linux Kernel sources/kernel/cgroup/cgroup.c cgroup_init_early(),
1554
                 * MAX_CGROUP_TYPE_NAMELEN check for details on why 32 is used.
1555
                 */
1556
                err = fscanf(proc_cgrp, "%32s %d %d %d", subsys_name, &hierarchy, &num_cgrps,
21,615✔
1557
                             &enabled);
1558
                if (err < 0)
21,615✔
1559
                        break;
1,441✔
1560

1561
                controllers[i] = strdup(subsys_name);
20,174✔
1562
                if (controllers[i] == NULL) {
20,174✔
1563
                        last_errno = errno;
×
1564
                        ret = ECGOTHER;
×
1565
                        break;
×
1566
                }
1567
                i++;
20,174✔
1568
        }
1569

1570
err:
×
1571
        if (proc_cgrp)
1,441✔
1572
                fclose(proc_cgrp);
1,441✔
1573

1574
        if (buf)
1,441✔
1575
                free(buf);
1,441✔
1576

1577
        if (ret != 0) {
1,441✔
1578
                for (i = 0; controllers[i]; i++) {
×
1579
                        free(controllers[i]);
×
1580
                        controllers[i] = NULL;
×
1581
                }
1582
        }
1583

1584
        return ret;
1,441✔
1585
}
1586

1587
/*
1588
 * Reads /proc/self/mounts and populates the cgroup v1/v2 mount points into the
1589
 * global cg_mount_table.
1590
 * This function should be called with cg_mount_table_lock taken.
1591
 */
1592
static int cgroup_populate_mount_points(char *controllers[CG_CONTROLLER_MAX])
1,441✔
1593
{
1594
        char mntent_buffer[4 * FILENAME_MAX];
1595
        struct mntent *ent, *temp_ent = NULL;
1,441✔
1596
        int found_mnt = 0;
1,441✔
1597
        FILE *proc_mount;
1598
        int ret = 0;
1,441✔
1599

1600
        proc_mount = fopen("/proc/self/mounts", "re");
1,441✔
1601
        if (proc_mount == NULL) {
1,441✔
1602
                cgroup_err("cannot open /proc/self/mounts: %s\n", strerror(errno));
×
1603
                last_errno = errno;
×
1604
                ret = ECGOTHER;
×
1605
                goto err;
×
1606
        }
1607

1608
        temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
1,441✔
1609
        if (!temp_ent) {
1,441✔
1610
                last_errno = errno;
×
1611
                ret = ECGOTHER;
×
1612
                goto err;
×
1613
        }
1614

1615
        while ((ent = getmntent_r(proc_mount, temp_ent,        mntent_buffer,
51,420✔
1616
                                  sizeof(mntent_buffer))) != NULL) {
51,420✔
1617

1618
                if (strcmp(ent->mnt_type, "cgroup") == 0) {
49,983✔
1619
                        if (controllers[0] == NULL) {
364✔
1620
                                cgroup_err("cgroup v1 requires /proc/cgroups, check if /proc ");
×
1621
                                cgroup_cont("is mounted with subset=pid option.\n");
×
1622
                                ret = ECGINVAL;
×
1623
                                goto err;
×
1624
                        }
1625

1626
                        ret = cgroup_process_v1_mnt(controllers, ent, &found_mnt);
364✔
1627
                        if (ret)
364✔
1628
                                goto err;
×
1629

1630
                        if (found_mnt >= CG_CONTROLLER_MAX)
364✔
1631
                                break;
4✔
1632

1633
                        continue;
360✔
1634
                }
1635

1636
                if (strcmp(ent->mnt_type, "cgroup2") == 0) {
49,619✔
1637
                        ret = cgroup_process_v2_mnt(ent, &found_mnt);
1,441✔
1638
                        if (ret == ECGEOF) {
1,441✔
1639
                        /* The controllers file was empty.  Ignore and move on. */
1640
                                ret = 0;
×
1641
                                continue;
×
1642
                        }
1643

1644
                        if (ret)
1,441✔
1645
                                goto err;
×
1646

1647
                        if (found_mnt >= CG_CONTROLLER_MAX)
1,441✔
1648
                                break;
×
1649
                }
1650
        }
1651

1652
        if (!found_mnt)
1,441✔
1653
                ret = ECGROUPNOTMOUNTED;
×
1654

1655
        if (found_mnt >= CG_CONTROLLER_MAX) {
1,441✔
1656
                cgroup_err("Mount points exceeds CG_CONTROLLER_MAX");
4✔
1657
                ret = ECGMAXVALUESEXCEEDED;
4✔
1658
                /*
1659
                 * There are loops in the libcgroup codebase that expect
1660
                 * there to be a null name entry at the end of the
1661
                 * cg_mount_table[].
1662
                 */
1663
                cg_mount_table[CG_CONTROLLER_MAX - 1].name[0] = '\0';
4✔
1664
        }
1665

1666
err:
1,437✔
1667
        if (proc_mount)
1,441✔
1668
                fclose(proc_mount);
1,441✔
1669

1670
        if (temp_ent)
1,441✔
1671
                free(temp_ent);
1,441✔
1672

1673
        return ret;
1,441✔
1674
}
1675

1676
/**
1677
 * cgroup_init(), initializes the MOUNT_POINT.
1678
 *
1679
 * This code is theoretically thread safe now. Its not really tested so it can
1680
 * blow up. If does for you, please let us know with your test case and we can
1681
 * really make it thread safe.
1682
 */
1683
int cgroup_init(void)
1,441✔
1684
{
1685
        static char *controllers[CG_CONTROLLER_MAX];
1686
        int ret = 0;
1,441✔
1687
        int i;
1688

1689
        cgroup_set_default_logger(-1);
1,441✔
1690

1691
        pthread_rwlock_wrlock(&cg_mount_table_lock);
1,441✔
1692

1693
        /* Free global variables filled by previous cgroup_init() */
1694
        cgroup_free_cg_mount_table();
1,441✔
1695

1696
        ret = cgroup_populate_controllers(controllers);
1,441✔
1697
        if (ret)
1,441✔
1698
                goto unlock_exit;
×
1699

1700
        ret = cgroup_populate_mount_points(controllers);
1,441✔
1701
        if (ret)
1,441✔
1702
                goto unlock_exit;
4✔
1703

1704
        cgroup_initialized = 1;
1,437✔
1705

1706
unlock_exit:
1,441✔
1707
        for (i = 0; controllers[i]; i++) {
21,615✔
1708
                free(controllers[i]);
20,174✔
1709
                controllers[i] = NULL;
20,174✔
1710
        }
1711

1712
        pthread_rwlock_unlock(&cg_mount_table_lock);
1,441✔
1713

1714
        return ret;
1,441✔
1715
}
1716

1717
static int cg_test_mounted_fs(void)
986✔
1718
{
1719
        char mntent_buff[4 * FILENAME_MAX];
1720
        struct mntent *temp_ent = NULL;
986✔
1721
        struct mntent *ent = NULL;
986✔
1722
        FILE *proc_mount = NULL;
986✔
1723
        int ret = 1;
986✔
1724

1725
        proc_mount = fopen("/proc/self/mounts", "re");
986✔
1726
        if (proc_mount == NULL)
986✔
1727
                return 0;
×
1728

1729
        temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
986✔
1730
        if (!temp_ent) {
986✔
1731
                /* We just fail at the moment. */
1732
                fclose(proc_mount);
×
1733
                return 0;
×
1734
        }
1735

1736
        ent = getmntent_r(proc_mount, temp_ent, mntent_buff, sizeof(mntent_buff));
986✔
1737
        if (!ent) {
986✔
1738
                ret = 0;
×
1739
                goto done;
×
1740
        }
1741

1742
        while (strcmp(ent->mnt_type, "cgroup") != 0 &&
12,857✔
1743
               strcmp(ent->mnt_type, "cgroup2") != 0) {
12,857✔
1744
                ent = getmntent_r(proc_mount, temp_ent, mntent_buff, sizeof(mntent_buff));
11,871✔
1745
                if (ent == NULL) {
11,871✔
1746
                        ret = 0;
×
1747
                        goto done;
×
1748
                }
1749
        }
1750
done:
986✔
1751
        fclose(proc_mount);
986✔
1752
        free(temp_ent);
986✔
1753

1754
        return ret;
986✔
1755
}
1756

1757
static inline pid_t cg_gettid(void)
2✔
1758
{
1759
        return syscall(__NR_gettid);
2✔
1760
}
1761

1762
static char *cg_concat_path(const char *pref, const char *suf, char *path)
26,915✔
1763
{
1764
        if ((suf[strlen(suf)-1] == '/') || ((strlen(suf) == 0) && (pref[strlen(pref)-1] == '/')))
26,915✔
1765
                snprintf(path, FILENAME_MAX, "%s%s", pref, suf+((suf[0] == '/') ? 1 : 0));
119✔
1766
        else
1767
                snprintf(path, FILENAME_MAX, "%s%s/", pref, suf+((suf[0] == '/') ? 1 : 0));
26,796✔
1768

1769
        path[FILENAME_MAX-1] = '\0';
26,915✔
1770

1771
        return path;
26,915✔
1772
}
1773

1774
/* Call with cg_mount_table_lock taken */
1775
/* path value have to have size at least FILENAME_MAX */
1776
char *cg_build_path_locked(const char *name, char *path, const char *type)
27,567✔
1777
{
1778
        char *tmp_systemd_default_cgrp, *_path = NULL;
27,567✔
1779
        /*
1780
         * len is the allocation size for path, that stores:
1781
         * cg_mount_table[i].mount.path + '/' + cg_namespace_table[i] + '/'
1782
         */
1783
        int i, ret, len = (FILENAME_MAX * 2) + 2;
27,567✔
1784

1785
        /*
1786
         * systemd_default_cgroup can't be clobbered.   The user may pass
1787
         * multiple cgroups, hence use temporary variable for manipulations
1788
         * for example:
1789
         * cgget -g cpu:/ -g cpu:cgrp1 -g cpu:/cgrp2
1790
         */
1791
        tmp_systemd_default_cgrp = calloc(1, (sizeof(char) * len));
27,567✔
1792
        if (!tmp_systemd_default_cgrp) {
27,567✔
1793
                cgroup_err("Failed to allocate memory for tmp_systemd_default_cgroup\n");
×
1794
                goto out;
×
1795
        }
1796

1797
#ifdef WITH_SYSTEMD
1798
        /*
1799
         * If the user specifies the name as /<cgroup-name>, they are
1800
         * effectively overriding the systemd_default_cgroup but if the name
1801
         * is "/", the cgroup root path is systemd_default_cgroup
1802
         */
1803
        if (strlen(systemd_default_cgroup) && name && name[0] == '/' && name[1] != '\0')
14,633✔
1804
                tmp_systemd_default_cgrp[0] = '\0';
1805
        else
1806
                snprintf(tmp_systemd_default_cgrp, len, "%s/", systemd_default_cgroup);
14,633✔
1807

1808
        /* allocate more space for systemd_default_cgroup + '/' */
1809
        len += (FILENAME_MAX + 1);
14,633✔
1810
#endif
1811
        /*
1812
         * Recent gcc are unhappy when sizeof(dest) <= sizeof(src) with
1813
         * snprintf()'s.  Alternative is to use multiple strncpy()/strcat(),
1814
         * work around it by allocating large temporary buffer _path and
1815
         * copying the constructed _path into path.
1816
         */
1817
        _path = malloc(len);
27,567✔
1818
        if (!_path) {
27,567✔
1819
                cgroup_err("Failed to allocate memory for _path\n");
×
1820
                goto out;
×
1821
        }
1822

1823
        /*
1824
         * If no type is specified, and there's a valid cgroup v2 mount, then
1825
         * build up a path to this mount (and cgroup name if supplied).
1826
         * This can be used to create a cgroup v2 cgroup that's not attached to
1827
         * any controller.
1828
         */
1829
        if (!type && strlen(cg_cgroup_v2_mount_path) > 0) {
27,567✔
1830
                ret = snprintf(_path, len, "%s/%s", cg_cgroup_v2_mount_path,
125✔
1831
                               tmp_systemd_default_cgrp);
1832
                if (ret >= FILENAME_MAX)
125✔
1833
                        cgroup_dbg("filename too long: %s", _path);
×
1834

1835
                strncpy(path, _path, FILENAME_MAX - 1);
125✔
1836
                path[FILENAME_MAX - 1] = '\0';
125✔
1837

1838
                if (name) {
125✔
1839
                        char *tmp;
1840

1841
                        tmp = strdup(path);
125✔
1842
                        if (tmp == NULL) {
125✔
1843
                                path = NULL;
×
1844
                                goto out;
×
1845
                        }
1846

1847
                        cg_concat_path(tmp, name, path);
125✔
1848
                        free(tmp);
125✔
1849
                }
1850
                goto out;
125✔
1851
        }
1852

1853
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
90,527✔
1854
                /* Two ways to successfully move forward here:
1855
                 * 1. The "type" controller matches the name of a mounted
1856
                 *    controller
1857
                 * 2. The "type" controller requested is "cgroup" and there's
1858
                 *    a "real" controller mounted as cgroup v2
1859
                 */
1860
                if ((type && strcmp(cg_mount_table[i].name, type) == 0) ||
90,525✔
1861
                    (type && strcmp(type, CGRP_FILE_PREFIX) == 0 &&
63,439✔
1862
                     cg_mount_table[i].version == CGROUP_V2)) {
354✔
1863

1864
                        if (cg_namespace_table[i])
27,440✔
1865
                                ret = snprintf(_path, len, "%s/%s%s/", cg_mount_table[i].mount.path,
4✔
1866
                                               tmp_systemd_default_cgrp, cg_namespace_table[i]);
1867
                        else
1868
                                ret = snprintf(_path, len, "%s/%s", cg_mount_table[i].mount.path,
27,436✔
1869
                                               tmp_systemd_default_cgrp);
1870

1871
                        if (ret >= FILENAME_MAX)
27,440✔
1872
                                cgroup_dbg("filename too long: %s", _path);
×
1873

1874
                        strncpy(path, _path, FILENAME_MAX - 1);
27,440✔
1875
                        path[FILENAME_MAX - 1] = '\0';
27,440✔
1876

1877
                        if (name) {
27,440✔
1878
                                char *tmp;
1879

1880
                                tmp = strdup(path);
26,790✔
1881
                                if (tmp == NULL)
26,790✔
1882
                                        break;
×
1883

1884
                                cg_concat_path(tmp, name, path);
26,790✔
1885
                                free(tmp);
26,790✔
1886
                        }
1887
                        goto out;
27,440✔
1888
                }
1889
        }
1890
        path = NULL;
2✔
1891

1892
out:
27,567✔
1893
        if (_path)
27,567✔
1894
                free(_path);
27,567✔
1895

1896
        if (tmp_systemd_default_cgrp)
27,567✔
1897
                free(tmp_systemd_default_cgrp);
27,567✔
1898

1899
        return path;
27,567✔
1900
}
1901

1902
char *cg_build_path(const char *name, char *path, const char *type)
1,849✔
1903
{
1904
        pthread_rwlock_rdlock(&cg_mount_table_lock);
1,849✔
1905
        path = cg_build_path_locked(name, path, type);
1,849✔
1906
        pthread_rwlock_unlock(&cg_mount_table_lock);
1,849✔
1907

1908
        return path;
1,849✔
1909
}
1910

1911
static int cgroup_get_cg_type(const char * const path, char * const type,
561✔
1912
                              size_t type_sz, bool is_tid)
1913
{
1914
        char cg_type_path[FILENAME_MAX];
1915
        char cg_type[CGV2_CONTROLLERS_LL_MAX];
1916
        int len, err = 0;
561✔
1917
        FILE *fp = NULL;
561✔
1918

1919
        snprintf(cg_type_path, FILENAME_MAX, "%scgroup.type", path);
561✔
1920
        fp = fopen(cg_type_path, "re");
561✔
1921
        if (!fp) {
561✔
1922
                if (errno == ENOENT) {
128✔
1923
                        /* file cgroup.type, doesn't exist for root cgroup. */
1924
                        snprintf(type, type_sz, "cgroup.procs");
128✔
1925
                        goto out;
128✔
1926
                } else {
1927
                        cgroup_warn("failed to open file %s: %s\n", cg_type_path, strerror(errno));
×
1928
                        err = ECGOTHER;
×
1929
                        goto out;
×
1930
                }
1931
        }
1932

1933
        if (fgets(cg_type, CGV2_CONTROLLERS_LL_MAX, fp) == NULL) {
433✔
1934
                cgroup_warn("failed to read file %s: %s\n", cg_type_path, strerror(errno));
×
1935
                err = ECGOTHER;
×
1936
                goto out;
×
1937
        }
1938

1939
        len = strlen(cg_type) - 1;
433✔
1940
        /*
1941
         * Append cgroup.threads to the path, if the cgroup.type is 'threaded'
1942
         * or 'domain threaded', with is_tid set. For cgroup.type 'domain' or
1943
         * 'domain invalid' or 'domain threaded', with is_tid is unset, append
1944
         * cgroup.procs to the path.
1945
         *
1946
         * domain type is used for regular cgroup and domain threaded for root
1947
         * of threaded cgroup v2 subtree. Another possible type is domain invalid,
1948
         * it's an invalid state, under the threaded subtree. is_tid is set when
1949
         * called from cgroup_attach_thread_tid() or unset other wise.
1950
         * Refer to Kernel's cgroup v2 documentation for more detailed explanation
1951
         * on domains types.
1952
         */
1953
        if (strncmp(cg_type, "domain", len) == 0         ||
433✔
1954
            strncmp(cg_type, "domain invalid", len) == 0 ||
20✔
1955
            (!is_tid && strncmp(cg_type, "domain threaded", len) == 0)) {
13✔
1956
                snprintf(type, type_sz, "cgroup.procs");
429✔
1957
        } else if (strncmp(cg_type, "threaded", len) == 0 ||
4✔
1958
                   (is_tid && strncmp(cg_type, "domain threaded", len) == 0)) {
×
1959
                snprintf(type, type_sz, "cgroup.threads");
4✔
1960
        } else {
1961
                cgroup_warn("invalid %scgroup.type: %s\n", path, cg_type);
×
1962
                err = ECGOTHER;
×
1963
        }
1964

1965
out:
561✔
1966
        if (fp)
561✔
1967
                fclose(fp);
433✔
1968

1969
        return err;
561✔
1970
}
1971

1972
int cgroup_build_tasks_procs_path(char * const path, size_t path_sz, const char * const cg_name,
565✔
1973
                                  const char * const ctrl_name)
1974
{
1975
        enum cg_version_t version;
1976
        char cg_type[CGV2_CONTROLLERS_LL_MAX];
1977
        int err = ECGOTHER;
565✔
1978

1979
        if (!cg_build_path(cg_name, path, ctrl_name))
565✔
1980
                goto error;
1✔
1981

1982
        err = cgroup_get_controller_version(ctrl_name, &version);
564✔
1983
        if (err)
564✔
1984
                goto error;
×
1985

1986
        switch (version) {
564✔
1987
        case CGROUP_V1:
2✔
1988
                strncat(path, "tasks", path_sz - strlen(path));
2✔
1989
                err = 0;
2✔
1990
                break;
2✔
1991
        case CGROUP_V2:
561✔
1992
                err = cgroup_get_cg_type(path, cg_type, sizeof(cg_type), 0);
561✔
1993
                if (err)
561✔
1994
                        goto error;
×
1995

1996
                strncat(path, cg_type, path_sz - strlen(path));
561✔
1997
                break;
561✔
1998
        default:
1✔
1999
                err = ECGOTHER;
1✔
2000
                break;
1✔
2001
        }
2002

2003
error:
565✔
2004
        if (err)
565✔
2005
                path[0] = '\0';
2✔
2006

2007
        cgroup_dbg("cgroup build procs path: %s\n", path);
565✔
2008

2009
        return err;
565✔
2010
}
2011

2012
STATIC int cgroupv2_controller_enabled(const char * const cg_name, const char * const ctrl_name)
83✔
2013
{
2014
        char path[FILENAME_MAX] = {0};
83✔
2015
        char *parent = NULL, *dname;
83✔
2016
        enum cg_version_t version;
2017
        bool enabled;
2018
        int error;
2019

2020
        error = cgroup_get_controller_version(ctrl_name, &version);
83✔
2021
        if (error)
83✔
2022
                return error;
×
2023

2024
        if (version != CGROUP_V2)
83✔
2025
                return 0;
1✔
2026

2027
        if (ctrl_name == NULL)
82✔
2028
                /* cgroup v2 supports cgroups with no controllers. */
2029
                return 0;
6✔
2030

2031
        if (strncmp(cg_name, "/", strlen(cg_name)) == 0)
76✔
2032
                /*
2033
                 * The root cgroup has been requested.  All version 2
2034
                 * controllers are enabled on the root cgroup.
2035
                 */
2036
                return 0;
1✔
2037

2038
        if (!cg_build_path(cg_name, path, ctrl_name))
75✔
2039
                goto err;
×
2040

2041
        parent = strdup(path);
75✔
2042
        if (!parent) {
75✔
2043
                error = ECGOTHER;
×
2044
                goto err;
×
2045
        }
2046

2047
        dname = dirname(parent);
75✔
2048

2049
        error = cgroupv2_get_subtree_control(dname, ctrl_name, &enabled);
75✔
2050
        if (error)
75✔
2051
                goto err;
2✔
2052

2053
        if (enabled)
73✔
2054
                error = 0;
73✔
2055
err:
×
2056
        if (parent)
75✔
2057
                free(parent);
75✔
2058

2059
        return error;
75✔
2060
}
2061

2062
static int __cgroup_attach_task_pid(char *path, pid_t tid)
95✔
2063
{
2064
        FILE *tasks = NULL;
95✔
2065
        int ret = 0;
95✔
2066

2067
        tasks = fopen(path, "we");
95✔
2068
        if (!tasks) {
95✔
2069
                switch (errno) {
2✔
2070
                case EPERM:
×
2071
                        ret = ECGROUPNOTOWNER;
×
2072
                        break;
×
2073
                case ENOENT:
2✔
2074
                        ret = ECGROUPNOTEXIST;
2✔
2075
                        break;
2✔
2076
                default:
×
2077
                        ret = ECGROUPNOTALLOWED;
×
2078
                }
2079
                goto err;
2✔
2080
        }
2081
        ret = fprintf(tasks, "%d", tid);
93✔
2082
        if (ret < 0) {
93✔
2083
                last_errno = errno;
×
2084
                ret = ECGOTHER;
×
2085
                goto err;
×
2086
        }
2087
        ret = fflush(tasks);
93✔
2088
        if (ret) {
93✔
2089
                last_errno = errno;
2✔
2090
                ret = ECGOTHER;
2✔
2091
                goto err;
2✔
2092
        }
2093
        fclose(tasks);
91✔
2094
        return 0;
91✔
2095
err:
4✔
2096
        cgroup_warn("cannot write tid %d to %s:%s\n", tid, path, strerror(errno));
4✔
2097
        if (tasks)
4✔
2098
                fclose(tasks);
2✔
2099
        return ret;
4✔
2100
}
2101

2102
static int cgroup_build_tid_path(const char * const ctrl_name, char *path)
×
2103
{
2104
        char cg_type[CGV2_CONTROLLERS_LL_MAX];
2105
        enum cg_version_t version;
2106
        size_t len;
2107
        int ret;
2108

2109
        ret = cgroup_get_controller_version(ctrl_name, &version);
×
2110
        if (ret)
×
2111
                return ret;
×
2112

2113
        if (version == CGROUP_V2) {
×
2114

2115
                len = strlen(path) - 12;
×
2116
                if (strncmp(path + len, "cgroup.procs", 12) != 0) {
×
2117
                        ret = ECGOTHER;
×
2118
                        return ret;
×
2119
                }
2120

2121
                /* right trim cgroup.procs file name in the path */
2122
                path[len] = '\0';
×
2123

2124
                ret = cgroup_get_cg_type(path, cg_type, sizeof(cg_type), 1);
×
2125
                if (ret)
×
2126
                        return ret;
×
2127

2128
                strncat(path, cg_type, FILENAME_MAX - (len + 1));
×
2129
                path[FILENAME_MAX - 1] = '\0';
×
2130
        }
2131

2132
        if (version != CGROUP_V1)
×
2133
                return ret;
×
2134

2135
        /* replace tasks with cgroup.procs file name in the path */
2136
        len = strlen(path) - 5;
×
2137
        path[len] = '\0';
×
2138

2139
        strncat(path, "cgroup.procs", FILENAME_MAX - (len + 1));
×
2140
        path[FILENAME_MAX - 1] = '\0';
×
2141

2142
        return ret;
×
2143
}
2144

2145
static int cgroup_attach_task_tid(struct cgroup *cgrp, pid_t tid, bool move_tids)
59✔
2146
{
2147
        char path[FILENAME_MAX] = {0};
59✔
2148
        char *controller_name = NULL;
59✔
2149
        int empty_cgrp = 0;
59✔
2150
        int i, ret = 0;
59✔
2151

2152
        if (!cgroup_initialized) {
59✔
2153
                cgroup_warn("libcgroup is not initialized\n");
×
2154
                return ECGROUPNOTINITIALIZED;
×
2155
        }
2156

2157
        /* if the cgroup is NULL, attach the task to the root cgroup. */
2158
        if (!cgrp) {
59✔
2159
                pthread_rwlock_rdlock(&cg_mount_table_lock);
2✔
2160
                for (i = 0; i < CG_CONTROLLER_MAX && cg_mount_table[i].name[0] != '\0'; i++) {
20✔
2161
                        ret = cgroup_build_tasks_procs_path(path, sizeof(path), NULL,
18✔
2162
                                                            cg_mount_table[i].name);
18✔
2163
                        if (ret)
18✔
2164
                                return ret;
×
2165

2166
                        if (move_tids) {
18✔
2167
                                ret = cgroup_build_tid_path(controller_name, path);
×
2168
                                if (ret)
×
2169
                                        return ret;
×
2170
                        }
2171

2172
                        ret = __cgroup_attach_task_pid(path, tid);
18✔
2173
                        if (ret) {
18✔
2174
                                pthread_rwlock_unlock(&cg_mount_table_lock);
×
2175
                                return ret;
×
2176
                        }
2177
                }
2178
                pthread_rwlock_unlock(&cg_mount_table_lock);
2✔
2179
        } else {
2180
                for (i = 0; i < cgrp->index; i++) {
128✔
2181
                        if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name)) {
71✔
2182
                                cgroup_warn("subsystem %s is not mounted\n",
×
2183
                                            cgrp->controller[i]->name);
2184
                                return ECGROUPSUBSYSNOTMOUNTED;
×
2185
                        }
2186
                }
2187

2188
                if (cgrp->index == 0)
57✔
2189
                        /* Valid empty cgroup v2 with no controllers added. */
2190
                        empty_cgrp = 1;
6✔
2191

2192
                for (i = 0, controller_name = NULL;
57✔
2193
                     empty_cgrp > 0 || i < cgrp->index;
130✔
2194
                     i++, empty_cgrp--) {
73✔
2195

2196
                        if (cgrp->controller[i])
77✔
2197
                                controller_name = cgrp->controller[i]->name;
71✔
2198

2199
                        ret = cgroupv2_controller_enabled(cgrp->name, controller_name);
77✔
2200
                        if (ret)
77✔
2201
                                return ret;
×
2202

2203
                        ret = cgroup_build_tasks_procs_path(path, sizeof(path), cgrp->name,
77✔
2204
                                                            controller_name);
2205
                        if (ret)
77✔
2206
                                return ret;
×
2207

2208
                        if (move_tids) {
77✔
2209
                                ret = cgroup_build_tid_path(controller_name, path);
×
2210
                                if (ret)
×
2211
                                        return ret;
×
2212
                        }
2213

2214
                        ret = __cgroup_attach_task_pid(path, tid);
77✔
2215
                        if (ret)
77✔
2216
                                return ret;
4✔
2217
                }
2218
        }
2219
        return 0;
55✔
2220
}
2221

2222
/**
2223
 *  cgroup_attach_task_pid is used to assign tasks to a cgroup.
2224
 *  struct cgroup *cgroup: The cgroup to assign the thread to.
2225
 *  pid_t tid: The thread to be assigned to the cgroup.
2226
 *
2227
 *  returns 0 on success.
2228
 *  returns ECGROUPNOTOWNER if the caller does not have access to the cgroup.
2229
 *  returns ECGROUPNOTALLOWED for other causes of failure.
2230
 */
2231
int cgroup_attach_task_pid(struct cgroup *cgroup, pid_t tid)
57✔
2232
{
2233
        return cgroup_attach_task_tid(cgroup, tid, 0);
57✔
2234
}
2235

2236
/**
2237
 * cgroup_attach_task is used to attach the current thread to a cgroup.
2238
 * struct cgroup *cgroup: The cgroup to assign the current thread to.
2239
 *
2240
 * See cg_attach_task_pid for return values.
2241
 */
2242
int cgroup_attach_task(struct cgroup *cgroup)
2✔
2243
{
2244
        pid_t tid = cg_gettid();
2✔
2245

2246
        return cgroup_attach_task_tid(cgroup, tid, 0);
2✔
2247
}
2248

2249
/**
2250
 *  cgroup_attach_thread_tid is used to assign threads to a cgroup.
2251
 *  struct cgroup *cgroup: The cgroup to assign the thread to.
2252
 *  pid_t tid: The thread to be assigned to the cgroup.
2253
 *
2254
 *  returns 0 on success.
2255
 *  returns ECGROUPNOTOWNER if the caller does not have access to the cgroup.
2256
 *  returns ECGROUPNOTALLOWED for other causes of failure.
2257
 */
2258
int cgroup_attach_thread_tid(struct cgroup *cgroup, pid_t tid)
×
2259
{
2260
        return cgroup_attach_task_tid(cgroup, tid, 1);
×
2261
}
2262

2263
/**
2264
 * cg_mkdir_p, emulate the mkdir -p command (recursively creating paths)
2265
 * @path: path to create
2266
 */
2267
int cg_mkdir_p(const char *path)
496✔
2268
{
2269
        char *real_path = NULL;
496✔
2270
        char *tmp_path = NULL;
496✔
2271
        struct stat st;
2272
        int ret = 0;
496✔
2273
        int i = 0;
496✔
2274
        char pos;
2275

2276
        real_path = strdup(path);
496✔
2277
        if (!real_path) {
496✔
2278
                last_errno = errno;
×
2279
                return ECGOTHER;
×
2280
        }
2281

2282
        do {
2283
                while (real_path[i] != '\0' && real_path[i] == '/')
4,552✔
2284
                        i++;
2,344✔
2285

2286
                if (real_path[i] == '\0')
2,208✔
2287
                        break; /* The path ends with '/', ignore it. */
247✔
2288

2289
                while (real_path[i] != '\0' && real_path[i] != '/')
12,922✔
2290
                        i++;
10,961✔
2291

2292
                pos = real_path[i];
1,961✔
2293
                real_path[i] = '\0';                /* Temporarily overwrite "/" */
1,961✔
2294

2295
                /* 0775 == S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH */
2296
                ret = mkdir(real_path, 0775);
1,961✔
2297
                if (ret) {
1,961✔
2298
                        switch (errno) {
1,732✔
2299
                        case EEXIST:
1,732✔
2300
                                ret = 0;        /* Not fatal really */
1,732✔
2301
                                break;
1,732✔
2302
                        case EPERM:
×
2303
                                ret = ECGROUPNOTOWNER;
×
2304
                                goto done;
×
2305
                        case EROFS:
×
2306
                                /*
2307
                                 * Check if path exists, use tmp_path to
2308
                                 * keep Coverity happy
2309
                                 */
2310
                                tmp_path = real_path;
×
2311
                                ret = stat(tmp_path, &st);
×
2312
                                if (ret == 0)
×
2313
                                        break;        /* Path exists */
×
2314
                        default: /* fallthrough */
2315
                                ret = ECGROUPNOTALLOWED;
×
2316
                                goto done;
×
2317
                        }
2318
                }
2319
                real_path[i] = pos;
1,961✔
2320
        } while (real_path[i]);
1,961✔
2321

2322
done:
249✔
2323
        free(real_path);
496✔
2324

2325
        return ret;
496✔
2326
}
2327

2328
/*
2329
 * create_control_group()
2330
 * This is the basic function used to create the control group. This function
2331
 * just makes the group. It does not set any permissions, or any control values.
2332
 * The argument path is the fully qualified path name to make it generic.
2333
 */
2334
static int cg_create_control_group(const char *path)
496✔
2335
{
2336
        int error;
2337

2338
        if (!cg_test_mounted_fs())
496✔
2339
                return ECGROUPNOTMOUNTED;
×
2340

2341
        error = cg_mkdir_p(path);
496✔
2342
        return error;
496✔
2343
}
2344

2345
/*
2346
 * set_control_value()
2347
 * This is the low level function for putting in a value in a control file.
2348
 * This function takes in the complete path and sets the value in val in that file.
2349
 */
2350
static int cg_set_control_value(char *path, const char *val)
490✔
2351
{
2352
        char *str_val_start;
2353
        char *str_val;
2354
        int ctl_file;
2355
        size_t len;
2356
        char *pos;
2357

2358
        if (!cg_test_mounted_fs())
490✔
2359
                return ECGROUPNOTMOUNTED;
×
2360

2361
        ctl_file = open(path, O_RDWR | O_CLOEXEC);
490✔
2362

2363
        if (ctl_file == -1) {
490✔
2364
                if (errno == EPERM) {
×
2365
                        /*
2366
                         * We need to set the correct error value, does the
2367
                         * group exist but we don't have the subsystem mounted
2368
                         * at that point, or is it that the group does not exist.
2369
                         * So we check if the tasks file exist. Before that, we
2370
                         * need to extract the path.
2371
                         */
2372
                        char *path_dir_end;
2373
                        FILE *control_file;
2374
                        char *tasks_path;
2375

2376
                        path_dir_end = strrchr(path, '/');
×
2377
                        if (path_dir_end == NULL)
×
2378
                                return ECGROUPVALUENOTEXIST;
×
2379
                        path_dir_end = '\0';
×
2380

2381
                        /* task_path contain: $path/tasks */
2382
                        tasks_path = (char *)malloc(strlen(path) + 6 + 1);
×
2383
                        if (tasks_path == NULL) {
×
2384
                                last_errno = errno;
×
2385
                                return ECGOTHER;
×
2386
                        }
2387
                        strcpy(tasks_path, path);
×
2388
                        strcat(tasks_path, "/tasks");
×
2389

2390
                        /* Test tasks file for read flag */
2391
                        control_file = fopen(tasks_path, "re");
×
2392
                        if (!control_file) {
×
2393
                                if (errno == ENOENT) {
×
2394
                                        free(tasks_path);
×
2395
                                        return ECGROUPSUBSYSNOTMOUNTED;
×
2396
                                }
2397
                        } else {
2398
                                fclose(control_file);
×
2399
                        }
2400

2401
                        free(tasks_path);
×
2402
                        return ECGROUPNOTALLOWED;
×
2403
                }
2404
                return ECGROUPVALUENOTEXIST;
×
2405
        }
2406

2407
        /*
2408
         * Split the multiline value into lines.  One line is a special
2409
         * case of multiline value.
2410
         */
2411
        str_val = strdup(val);
490✔
2412
        if (str_val == NULL) {
490✔
2413
                last_errno = errno;
×
2414
                close(ctl_file);
×
2415
                return ECGOTHER;
×
2416
        }
2417

2418
        str_val_start = str_val;
490✔
2419
        pos = str_val;
490✔
2420

2421
        do {
2422
                str_val = pos;
502✔
2423
                pos = strchr(str_val, '\n');
502✔
2424

2425
                if (pos) {
502✔
2426
                        *pos = '\0';
12✔
2427
                        ++pos;
12✔
2428
                }
2429

2430
                len = strlen(str_val);
502✔
2431
                if (len > 0) {
502✔
2432
                        if (write(ctl_file, str_val, len) == -1) {
490✔
2433
                                last_errno = errno;
23✔
2434
                                free(str_val_start);
23✔
2435
                                close(ctl_file);
23✔
2436
                                return ECGOTHER;
23✔
2437
                        }
2438
                } else
2439
                        cgroup_warn("skipping empty line for %s\n", path);
12✔
2440
        } while (pos);
479✔
2441

2442
        if (close(ctl_file)) {
467✔
2443
                last_errno = errno;
×
2444
                free(str_val_start);
×
2445
                return ECGOTHER;
×
2446
        }
2447

2448
        free(str_val_start);
467✔
2449
        return 0;
467✔
2450
}
2451

2452
/**
2453
 * Walk the settings in controller and write their values to disk
2454
 *
2455
 * @param base The full path to the base of this cgroup
2456
 * @param controller The controller whose values are being updated
2457
 * @param ignore_non_dirty_values If set skips writing non-dirty controller settings
2458
 */
2459
STATIC int cgroup_set_values_recursive(const char * const base,
611✔
2460
                                       const struct cgroup_controller * const controller,
2461
                                       bool ignore_non_dirty_values)
2462
{
2463
        struct control_value *cv;
2464
        struct stat path_stat;
2465
        int ret, j, error = 0;
611✔
2466
        char *path = NULL;
611✔
2467

2468
        for (j = 0; j < controller->index; j++) {
1,005✔
2469
                cv = controller->values[j];
426✔
2470

2471
                /*
2472
                 * ignore_non_dirty_values is set while writing into
2473
                 * existing cgroup to modify controller settings and
2474
                 * unset during cgroup creation.  The subtle difference
2475
                 * is that dirty flag is unset for all the controller
2476
                 * settings during cgroup creation, whereas some or all
2477
                 * controller settings have the dirty flag set during
2478
                 * modification.
2479
                 *
2480
                 * Be careful with ignore_non_dirty_values flag, setting
2481
                 * it writing only the controller settings that has it
2482
                 * dirty value set.
2483
                 */
2484
                if (ignore_non_dirty_values && cv->dirty == false)
426✔
2485
                        continue;
×
2486

2487
                /* We don't support, writing multiline settings */
2488
                if (strcspn(cv->value, "\n")  < (strlen(cv->value) - 1))
426✔
2489
                        continue;
×
2490

2491
                ret = asprintf(&path, "%s%s", base, cv->name);
426✔
2492
                if (ret < 0) {
426✔
2493
                        last_errno = errno;
×
2494
                        error = ECGOTHER;
×
2495
                        goto err;
×
2496
                }
2497

2498
                /* skip read-only settings */
2499
                ret = (stat(path, &path_stat));
426✔
2500
                if (ret < 0) {
426✔
2501
                        last_errno = errno;
10✔
2502
                        error = ECGROUPVALUENOTEXIST;
10✔
2503
                        goto err;
10✔
2504
                }
2505

2506
                /* 0200 == S_IWUSR */
2507
                if (!(path_stat.st_mode & 0200)) {
416✔
2508
                        free(path);
×
2509
                        path = NULL;
×
2510
                        continue;
×
2511
                }
2512

2513
                cgroup_dbg("setting %s to \"%s\", pathlen %d\n", path, cv->value, ret);
416✔
2514

2515
                error = cg_set_control_value(path, cv->value);
416✔
2516
                free(path);
416✔
2517
                path = NULL;
416✔
2518

2519
                if (error) {
416✔
2520
                        /* Ignore the errors on deprecated settings */
2521
                        if (last_errno == EOPNOTSUPP) {
22✔
2522
                                error = 0;
×
2523
                                continue;
×
2524
                        }
2525
                        goto err;
22✔
2526
                }
2527
                cv->dirty = false;
394✔
2528
        }
2529

2530
err:
579✔
2531
        /*
2532
         * As currently written, path should always be null as we are\
2533
         * exiting this function, but let's check just in case, and free it
2534
         * if it's non-null
2535
         */
2536
        if (path)
611✔
2537
                free(path);
10✔
2538

2539
        return error;
611✔
2540
}
2541

2542
/**
2543
 * Check if the requested cgroup controller is enabled in the specified file
2544
 *
2545
 * @param path Cgroup directory
2546
 * @param ctrl_name Name of the controller to check
2547
 * @param output parameter that indicates whether the controller is enabled
2548
 * @param file to open and parse
2549
 *        0 = cgroup.subtree_control
2550
 *        1 = cgroup.controllers
2551
 */
2552
STATIC int __cgroupv2_get_enabled(const char *path, const char *ctrl_name,
1,137✔
2553
                                  bool * const enabled, int file_enum)
2554
{
2555
        char *path_copy = NULL, *saveptr = NULL, *token, *ret_c, *filename;
1,137✔
2556
        int ret, error = ECGROUPNOTMOUNTED;
1,137✔
2557
        char buffer[FILENAME_MAX];
2558
        FILE *fp = NULL;
1,137✔
2559

2560
        if (!path || !ctrl_name || !enabled)
1,137✔
2561
                return ECGOTHER;
×
2562

2563
        *enabled = false;
1,137✔
2564

2565
        switch (file_enum) {
1,137✔
2566
        case 0: /* cgroup.subtree_control */
553✔
2567
                filename = CGV2_SUBTREE_CTRL_FILE;
553✔
2568
                break;
553✔
2569
        case 1: /* cgroup.controllers */
584✔
2570
                filename = CGV2_CONTROLLERS_FILE;
584✔
2571
                break;
584✔
2572
        default:
×
2573
                return ECGINVAL;
×
2574
        }
2575

2576
        path_copy = (char *)malloc(FILENAME_MAX);
1,137✔
2577
        if (!path_copy) {
1,137✔
2578
                error = ECGOTHER;
×
2579
                goto out;
×
2580
        }
2581

2582
        ret = snprintf(path_copy, FILENAME_MAX, "%s/%s", path, filename);
1,137✔
2583
        if (ret < 0) {
1,137✔
2584
                error = ECGOTHER;
×
2585
                goto out;
×
2586
        }
2587

2588
        fp = fopen(path_copy, "re");
1,137✔
2589
        if (!fp) {
1,137✔
2590
                cgroup_warn("fopen failed\n");
×
2591
                last_errno = errno;
×
2592
                error = ECGOTHER;
×
2593
                goto out;
×
2594
        }
2595

2596
        ret_c = fgets(buffer, sizeof(buffer), fp);
1,137✔
2597
        if (ret_c == NULL)
1,137✔
2598
                /* The subtree control file is empty */
2599
                goto out;
50✔
2600

2601
        /* Remove the trailing newline */
2602
        ret_c[strlen(ret_c) - 1] = '\0';
1,087✔
2603

2604
        /*
2605
         * Split the enabled controllers by " " and evaluate if the
2606
         * requested controller is enabled.
2607
         */
2608
        token = strtok_r(buffer, " ", &saveptr);
1,087✔
2609
        do {
2610
                if (strncmp(ctrl_name, token, FILENAME_MAX) == 0) {
3,314✔
2611
                        error = 0;
794✔
2612
                        *enabled = true;
794✔
2613
                        break;
794✔
2614
                }
2615
        } while ((token = strtok_r(NULL, " ", &saveptr)));
2,520✔
2616

2617
out:
293✔
2618
        if (path_copy)
1,137✔
2619
                free(path_copy);
1,137✔
2620
        if (fp)
1,137✔
2621
                fclose(fp);
1,137✔
2622

2623
        return error;
1,137✔
2624
}
2625

2626
/**
2627
 * Check if the requested cgroup controller is enabled on this subtree
2628
 *
2629
 * @param path Cgroup directory
2630
 * @param ctrl_name Name of the controller to check
2631
 * @param output parameter that indicates whether the controller is enabled
2632
 */
2633
STATIC int cgroupv2_get_subtree_control(const char *path, const char *ctrl_name,
553✔
2634
                                        bool * const enabled)
2635
{
2636
        return __cgroupv2_get_enabled(path, ctrl_name, enabled, 0);
553✔
2637
}
2638
/**
2639
 * Check if the requested cgroup controller is enabled in this cgroup's cgroup.controllers file
2640
 *
2641
 * @param path Cgroup directory
2642
 * @param ctrl_name Name of the controller to check
2643
 * @param output parameter that indicates whether the controller is enabled
2644
 */
2645
static int cgroupv2_get_controllers(const char *path, const char *ctrl_name,
584✔
2646
                                    bool * const enabled)
2647
{
2648
        return __cgroupv2_get_enabled(path, ctrl_name, enabled, 1);
584✔
2649
}
2650

2651
/**
2652
 * Enable/Disable a controller in the cgroup v2 subtree_control file
2653
 *
2654
 * @param path Directory that contains the subtree_control file
2655
 * @param ctrl_name Name of the controller to be enabled/disabled
2656
 * @param enable Enable/Disable the given controller
2657
 */
2658
STATIC int cgroupv2_subtree_control(const char *path, const char *ctrl_name, bool enable)
74✔
2659
{
2660
        int ret, error = ECGOTHER;
74✔
2661
        char *path_copy = NULL;
74✔
2662
        char *value = NULL;
74✔
2663

2664
        if (!path || !ctrl_name)
74✔
2665
                return ECGOTHER;
×
2666

2667
        value = (char *)malloc(FILENAME_MAX);
74✔
2668
        if (!value)
74✔
2669
                goto out;
×
2670

2671
        path_copy = (char *)malloc(FILENAME_MAX);
74✔
2672
        if (!path_copy)
74✔
2673
                goto out;
×
2674

2675
        ret = snprintf(path_copy, FILENAME_MAX, "%s/%s", path, CGV2_SUBTREE_CTRL_FILE);
74✔
2676
        if (ret < 0)
74✔
2677
                goto out;
×
2678

2679
        if (enable)
74✔
2680
                ret = snprintf(value, FILENAME_MAX, "+%s", ctrl_name);
73✔
2681
        else
2682
                ret = snprintf(value, FILENAME_MAX, "-%s", ctrl_name);
1✔
2683
        if (ret < 0)
74✔
2684
                goto out;
×
2685

2686
        error = cg_set_control_value(path_copy, value);
74✔
2687
        if (error)
74✔
2688
                goto out;
1✔
2689

2690
out:
73✔
2691
        if (value)
74✔
2692
                free(value);
74✔
2693
        if (path_copy)
74✔
2694
                free(path_copy);
74✔
2695
        return error;
74✔
2696
}
2697

2698
/*
2699
 * Test if the controller is enabled in the root_cgrp.subtree_control file
2700
 * and enable the controller, if not enabled.
2701
 */
2702
static int test_and_set_ctrl_mnt_path(const char * const mount_path, const char * const ctrl_name)
223✔
2703
{
2704
        bool enabled;
2705
        int ret;
2706

2707
        /* return if the controller is enabled */
2708
        ret = cgroupv2_get_subtree_control(mount_path, ctrl_name, &enabled);
223✔
2709
        if (ret == ECGOTHER)
223✔
2710
                return ret;
×
2711

2712
        if (enabled == true)
223✔
2713
                return 0;
213✔
2714

2715
        /* check if the controller is available is controllers file */
2716
        ret = cgroupv2_get_controllers(mount_path, ctrl_name, &enabled);
10✔
2717
        if (ret == ECGOTHER || ret == ECGROUPNOTMOUNTED)
10✔
2718
                return ret;
×
2719

2720
        /* enable the controller in the subtree_control file */
2721
        ret = cgroupv2_subtree_control(mount_path, ctrl_name, 1);
10✔
2722
        if (ret)
10✔
2723
                return ret;
×
2724

2725
        return 0;
10✔
2726
}
2727

2728
/**
2729
 * Recursively enable/disable a controller in the cgv2 subtree_control file
2730
 *
2731
 * @param path Directory that contains the subtree_control file
2732
 * @param ctrl_name Name of the controller to be enabled/disabled
2733
 * @param enable Enable/Disable the given controller
2734
 */
2735
STATIC int cgroupv2_subtree_control_recursive(char *path, const char *ctrl_name, bool enable)
223✔
2736
{
2737
        char *path_copy, *tmp_path, *stok_buff = NULL;
223✔
2738
        bool found_mount = false, controller_enabled = false;
223✔
2739
        size_t mount_len;
2740
        int i, error = 0;
223✔
2741

2742
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
583✔
2743
                if (strncmp(cg_mount_table[i].name, ctrl_name,
583✔
2744
                            sizeof(cg_mount_table[i].name)) == 0) {
2745
                        found_mount = true;
223✔
2746
                        break;
223✔
2747
                }
2748
        }
2749

2750
        if (!found_mount)
223✔
2751
                return ECGROUPSUBSYSNOTMOUNTED;
×
2752

2753
        path_copy = strdup(path);
223✔
2754
        if (!path_copy)
223✔
2755
                return ECGOTHER;
×
2756

2757
        /*
2758
         * Null terminate the path_copy to match the string length of the
2759
         * controller mount.  We'll incrementally build up the string, subdir
2760
         * by subdir, and enable the subtree control file each step of the way
2761
         */
2762
        mount_len = strlen(cg_mount_table[i].mount.path);
223✔
2763
        path_copy[mount_len] = '\0';
223✔
2764

2765
        /*
2766
         * systemd by default only enables cpu, cpuset, io, memory, and pids
2767
         * controller in the root_cgroup.subtree_control. Trying to enable
2768
         * hugetlb and misc controller in a nested cgroups, will fail
2769
         * because they are not, enabled the controller in the root_cgroup.
2770
         * i.e. # cgcreate -ghugetlb:foo/bar
2771
         *
2772
         * check if the controller is enabled in the
2773
         * root_cgroup.subtree_control file and if not enable it. Let's
2774
         * check for all controllers, so that it accommodates other
2775
         * controllers systemd decides to disable by default in the future.
2776
         */
2777
        error = test_and_set_ctrl_mnt_path(path_copy, ctrl_name);
223✔
2778
        if (error)
223✔
2779
                goto out;
×
2780

2781
        tmp_path = strtok_r(&path[mount_len], "/", &stok_buff);
223✔
2782
        do {
2783
                if (tmp_path) {
249✔
2784
                        strcat(path_copy, "/");
94✔
2785
                        strcat(path_copy, tmp_path);
94✔
2786
                }
2787

2788
                error = cg_create_control_group(path_copy);
249✔
2789
                if (error)
249✔
2790
                        goto out;
×
2791

2792
                error = cgroupv2_get_subtree_control(path_copy, ctrl_name, &controller_enabled);
249✔
2793
                if (controller_enabled)
249✔
2794
                        continue;
187✔
2795
                if (error != ECGROUPNOTMOUNTED)
62✔
2796
                        goto out;
×
2797

2798
                error = cgroupv2_subtree_control(path_copy, ctrl_name, enable);
62✔
2799
                if (error)
62✔
2800
                        goto out;
1✔
2801
        } while ((tmp_path = strtok_r(NULL, "/", &stok_buff)));
248✔
2802

2803
out:
222✔
2804
        free(path_copy);
223✔
2805
        return error;
223✔
2806
}
2807

2808
/**
2809
 * cgroup_modify_cgroup modifies the cgroup control files.
2810
 * struct cgroup *cgrp: The name will be the cgroup to be modified.
2811
 * The values will be the values to be modified, those not mentioned in the
2812
 * structure will not be modified.
2813
 *
2814
 * The uids cannot be modified yet.
2815
 *
2816
 * returns 0 on success.
2817
 */
2818

2819
int cgroup_modify_cgroup(struct cgroup *cgrp)
403✔
2820
{
2821
        char base[FILENAME_MAX];
2822
        int error = 0;
403✔
2823
        int i;
2824

2825
        if (!cgroup_initialized)
403✔
2826
                return ECGROUPNOTINITIALIZED;
×
2827

2828
        if (!cgrp)
403✔
2829
                return ECGROUPNOTALLOWED;
×
2830

2831
        for (i = 0; i < cgrp->index; i++) {
789✔
2832
                if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name)) {
386✔
2833
                        cgroup_warn("subsystem %s is not mounted\n", cgrp->controller[i]->name);
×
2834
                        return ECGROUPSUBSYSNOTMOUNTED;
×
2835
                }
2836
        }
2837

2838
        for (i = 0; i < cgrp->index; i++) {
777✔
2839
                if (!cg_build_path(cgrp->name, base, cgrp->controller[i]->name))
386✔
2840
                        continue;
×
2841

2842
                error = cgroup_set_values_recursive(base, cgrp->controller[i], true);
386✔
2843
                if (error)
386✔
2844
                        goto err;
12✔
2845
        }
2846
err:
391✔
2847
        return error;
403✔
2848
}
2849

2850
int cgroup_copy_controller_values(struct cgroup_controller * const dst,
648✔
2851
                                  const struct cgroup_controller * const src)
2852
{
2853
        int i, ret = 0;
648✔
2854

2855
        if (!dst || !src)
648✔
2856
                return ECGFAIL;
×
2857

2858
        strncpy(dst->name, src->name, CONTROL_NAMELEN_MAX);
648✔
2859
        for (i = 0; i < src->index; i++, dst->index++) {
1,298✔
2860
                struct control_value *src_val = src->values[i];
650✔
2861
                struct control_value *dst_val;
2862

2863
                dst->values[i] = calloc(1, sizeof(struct control_value));
650✔
2864
                if (!dst->values[i]) {
650✔
2865
                        last_errno = errno;
×
2866
                        ret = ECGOTHER;
×
2867
                        goto err;
×
2868
                }
2869

2870
                dst_val = dst->values[i];
650✔
2871
                strncpy(dst_val->value, src_val->value, CG_CONTROL_VALUE_MAX);
650✔
2872
                strncpy(dst_val->name, src_val->name, FILENAME_MAX);
650✔
2873

2874
                if (src_val->multiline_value) {
650✔
2875
                        dst_val->multiline_value = strdup(src_val->multiline_value);
×
2876
                        if (!dst_val->multiline_value) {
×
2877
                                last_errno = errno;
×
2878
                                ret = ECGOTHER;
×
2879
                                goto err;
×
2880
                        }
2881
                } else {
2882
                        dst_val->multiline_value = NULL;
650✔
2883
                }
2884

2885
                if (src_val->prev_name) {
650✔
2886
                        dst_val->prev_name = strdup(src_val->prev_name);
×
2887
                        if (!dst_val->prev_name) {
×
2888
                                last_errno = errno;
×
2889
                                ret = ECGOTHER;
×
2890
                                goto err;
×
2891
                        }
2892
                } else {
2893
                        dst_val->prev_name = NULL;
650✔
2894
                }
2895
                /*
2896
                 * set dirty flag unconditionally, as we overwrite
2897
                 * destination controller values.
2898
                 */
2899
                dst_val->dirty = true;
650✔
2900
        }
2901

2902
        return ret;
648✔
2903

2904
err:
×
2905
        dst->index = 0;
×
2906
        for (i = 0; i < src->index; i++) {
×
2907
                if (dst->values[i]) {
×
2908
                        if (dst->values[i]->multiline_value)
×
2909
                                free(dst->values[i]->multiline_value);
×
2910

2911
                        if (dst->values[i]->prev_name)
×
2912
                                free(dst->values[i]->prev_name);
×
2913

2914
                        free(dst->values[i]);
×
2915
                }
2916
        }
2917

2918
        return ret;
×
2919
}
2920

2921
/**
2922
 * @dst: Destination control group
2923
 * @src: Source from which values will be copied to dst
2924
 *
2925
 * Create a duplicate copy of src in dst. This will be useful for those who
2926
 * that intend to create new instances based on an existing control group
2927
 */
2928
int cgroup_copy_cgroup(struct cgroup *dst, struct cgroup *src)
409✔
2929
{
2930
        int ret = 0, i;
409✔
2931

2932
        if (!dst || !src)
409✔
2933
                return ECGROUPNOTEXIST;
×
2934

2935
        /* Should we just use the restrict keyword instead? */
2936
        if (dst == src)
409✔
2937
                return ECGFAIL;
×
2938

2939
        cgroup_free_controllers(dst);
409✔
2940

2941
        for (i = 0; i < src->index; i++, dst->index++) {
801✔
2942
                struct cgroup_controller *src_ctlr = src->controller[i];
392✔
2943
                struct cgroup_controller *dst_ctlr;
2944

2945
                dst->controller[i] = calloc(1, sizeof(struct cgroup_controller));
392✔
2946
                if (!dst->controller[i]) {
392✔
2947
                        last_errno = errno;
×
2948
                        ret = ECGOTHER;
×
2949
                        goto err;
×
2950
                }
2951

2952
                dst_ctlr = dst->controller[i];
392✔
2953
                ret = cgroup_copy_controller_values(dst_ctlr, src_ctlr);
392✔
2954
                if (ret)
392✔
2955
                        goto err;
×
2956
        }
2957
err:
409✔
2958
        return ret;
409✔
2959
}
2960

2961
/**
2962
 * Chown and chmod the tasks file in cg_path
2963
 *
2964
 * @param uid The UID that will own the tasks file
2965
 * @param gid The GID that will own the tasks file
2966
 * @param fperm The permissions to place on the tasks file
2967
 */
2968
STATIC int cgroup_chown_chmod_tasks(const char * const cg_path, uid_t uid, gid_t gid, mode_t fperm)
1✔
2969
{
2970
        int ret, error;
2971
        char *tasks_path = NULL;
1✔
2972

2973
        tasks_path = (char *)malloc(FILENAME_MAX);
1✔
2974
        if (tasks_path == NULL)
1✔
2975
                return ECGOTHER;
×
2976

2977
        ret = snprintf(tasks_path, FILENAME_MAX, "%s/tasks", cg_path);
1✔
2978
        if (ret < 0 || ret >= FILENAME_MAX) {
1✔
2979
                last_errno = errno;
×
2980
                error = ECGOTHER;
×
2981
                goto err;
×
2982
        }
2983

2984
        error = cg_chown(tasks_path, uid, gid);
1✔
2985
        if (!error && fperm != NO_PERMS)
1✔
2986
                error = cg_chmod_path(tasks_path, fperm, 1);
1✔
2987

2988
        if (error) {
1✔
2989
                last_errno = errno;
×
2990
                error = ECGOTHER;
×
2991
        }
2992

2993
err:
1✔
2994
        if (tasks_path)
1✔
2995
                free(tasks_path);
1✔
2996

2997
        return error;
1✔
2998
}
2999

3000
static int _cgroup_create_cgroup(const struct cgroup * const cgrp,
248✔
3001
                                 const struct cgroup_controller * const controller,
3002
                                 int ignore_ownership)
3003
{
3004
        enum cg_version_t version = CGROUP_UNK;
248✔
3005
        char *fts_path[2];
3006
        char *base = NULL;
248✔
3007
        char *path = NULL;
248✔
3008
        int error;
3009

3010
        fts_path[0] = (char *)malloc(FILENAME_MAX);
248✔
3011
        if (!fts_path[0]) {
248✔
3012
                last_errno = errno;
×
3013
                return ECGOTHER;
×
3014
        }
3015
        fts_path[1] = NULL;
248✔
3016
        path = fts_path[0];
248✔
3017

3018
        if (controller) {
248✔
3019
                if (!cg_build_path(cgrp->name, path, controller->name)) {
225✔
3020
                        error = ECGOTHER;
×
3021
                        goto err;
×
3022
                }
3023

3024
                error = cgroup_get_controller_version(controller->name, &version);
225✔
3025
                if (error)
225✔
3026
                        goto err;
×
3027

3028
                if (version == CGROUP_V2) {
225✔
3029
                        char *parent, *dname;
3030

3031
                        parent = strdup(path);
223✔
3032
                        if (!parent) {
223✔
3033
                                error = ECGOTHER;
×
3034
                                goto err;
×
3035
                        }
3036

3037
                        dname = dirname(parent);
223✔
3038

3039
                        error = cgroupv2_subtree_control_recursive(dname, controller->name, true);
223✔
3040
                        free(parent);
223✔
3041
                        if (error)
223✔
3042
                                goto err;
1✔
3043
                }
3044
        } else {
3045
                if (!cg_build_path(cgrp->name, path, NULL)) {
23✔
3046
                        error = ECGOTHER;
×
3047
                        goto err;
×
3048
                }
3049
        }
3050

3051
        error = cg_create_control_group(path);
247✔
3052
        if (error)
247✔
3053
                goto err;
×
3054

3055
        base = strdup(path);
247✔
3056

3057
        if (!base) {
247✔
3058
                last_errno = errno;
×
3059
                error = ECGOTHER;
×
3060
                goto err;
×
3061
        }
3062

3063
        if (!ignore_ownership) {
247✔
3064
                cgroup_dbg("Changing ownership of %s\n", fts_path[0]);
237✔
3065
                error = cg_chown_recursive(fts_path, cgrp->control_uid, cgrp->control_gid);
237✔
3066
                if (!error)
237✔
3067
                        error = cg_chmod_recursive_controller(fts_path[0],
237✔
3068
                                                              cgrp->control_dperm,
237✔
3069
                                                              cgrp->control_dperm != NO_PERMS,
237✔
3070
                                                              cgrp->control_fperm,
237✔
3071
                                                              cgrp->control_fperm != NO_PERMS,
237✔
3072
                                                              1, cgroup_ignored_tasks_files);
3073
        }
3074

3075
        if (error)
247✔
3076
                goto err;
×
3077

3078
        if (controller) {
247✔
3079
                error = cgroup_set_values_recursive(base, controller, false);
224✔
3080
                if (error)
224✔
3081
                        goto err;
20✔
3082
        }
3083

3084
        if (!ignore_ownership && version == CGROUP_V1) {
227✔
3085
                error = cgroup_chown_chmod_tasks(base, cgrp->tasks_uid, cgrp->tasks_gid,
×
3086
                                                 cgrp->task_fperm);
×
3087
                if (error)
×
3088
                        goto err;
×
3089
        }
3090
err:
227✔
3091
        if (path)
248✔
3092
                free(path);
248✔
3093
        if (base)
248✔
3094
                free(base);
247✔
3095

3096
        return error;
248✔
3097
}
3098

3099
/**
3100
 * cgroup_create_cgroup creates a new control group.
3101
 * struct cgroup *cgrp: The control group to be created
3102
 *
3103
 * returns 0 on success. We recommend calling cg_delete_cgroup if this
3104
 * routine fails. That should do the cleanup operation. If ECGCANTSETVALUE
3105
 * is returned, the group was created successfully but not all controller
3106
 * parameters were successfully set.
3107
 */
3108
int cgroup_create_cgroup(struct cgroup *cgrp, int ignore_ownership)
215✔
3109
{
3110
        int error = 0;
215✔
3111
        int i;
3112

3113
        if (!cgroup_initialized)
215✔
3114
                return ECGROUPNOTINITIALIZED;
×
3115

3116
        if (!cgrp)
215✔
3117
                return ECGROUPNOTALLOWED;
×
3118

3119
        for (i = 0; i < cgrp->index; i++) {
440✔
3120
                if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name))
225✔
3121
                        return ECGROUPSUBSYSNOTMOUNTED;
×
3122
        }
3123

3124
        if (cgrp->index == 0) {
215✔
3125
                /* Create an empty cgroup v2 cgroup */
3126
                error = _cgroup_create_cgroup(cgrp, NULL, ignore_ownership);
23✔
3127
                if (error)
23✔
3128
                        /*
3129
                         * Since we only attempted to create a single cgroup,
3130
                         * _cgroup_create_cgroup() can capably undo the failure and no
3131
                         * interventions are required here.
3132
                         */
3133
                        return error;
×
3134
        }
3135

3136
        /*
3137
         * XX: One important test to be done is to check, if you have
3138
         * multiple subsystems mounted at one point, all of them *have* be
3139
         * on the cgroup data structure. If not, we fail.
3140
         */
3141
        for (i = 0; i < cgrp->index; i++) {
419✔
3142
                error = _cgroup_create_cgroup(cgrp, cgrp->controller[i], ignore_ownership);
225✔
3143
                if (error) {
225✔
3144
                        int del_error;
3145

3146
                        /*
3147
                         * This will remove any cgroup directories that were made, but it won't
3148
                         * undo changes that have been written to the parent cgroup's
3149
                         * subtree_control file.  To safely undo changes there, we would need to
3150
                         * save the subtree_control file's previous value and restore it.
3151
                         */
3152
                        del_error = cgroup_delete_cgroup(cgrp, 1);
21✔
3153
                        if (del_error)
21✔
3154
                                cgroup_err("Failed to delete %s: %s\n", cgrp->name,
×
3155
                                           cgroup_strerror(del_error));
3156
                        return error;
21✔
3157
                }
3158
        }
3159

3160
        return 0;
194✔
3161
}
3162

3163
/**
3164
 * Obtain the calculated parent name of specified cgroup; no validation of
3165
 * the existence of the child or parent group is performed.
3166
 *
3167
 * Given the path-like hierarchy of cgroup names, this function returns the
3168
 * dirname() of the cgroup name as the likely parent name; the caller is
3169
 * responsible for validating parent as appropriate.
3170
 *
3171
 * @param cgrp The cgroup to query for parent's name
3172
 * @param parent Output, name of parent's group, or NULL if the
3173
 *        provided cgroup is the root group.
3174
 *        Caller is responsible to free the returned string.
3175
 * @return 0 on success, > 0 on error
3176
 */
3177
static int cgroup_get_parent_name(struct cgroup *cgrp, char **parent)
188✔
3178
{
3179
        char *pdir = NULL;
188✔
3180
        char *dir = NULL;
188✔
3181
        int ret = 0;
188✔
3182

3183
        dir = strdup(cgrp->name);
188✔
3184
        if (!dir) {
188✔
3185
                last_errno = errno;
×
3186
                return ECGOTHER;
×
3187
        }
3188
        cgroup_dbg("group name is %s\n", dir);
188✔
3189

3190
        pdir = dirname(dir);
188✔
3191
        cgroup_dbg("parent's group name is %s\n", pdir);
188✔
3192

3193
        /* Check for root group */
3194
        if (strlen(cgrp->name) == 0 || !strcmp(cgrp->name, pdir)) {
188✔
3195
                cgroup_dbg("specified cgroup \"%s\" is root group\n", cgrp->name);
×
3196
                *parent = NULL;
×
3197
        } else {
3198
                *parent = strdup(pdir);
188✔
3199
                if (*parent == NULL) {
188✔
3200
                        last_errno = errno;
×
3201
                        ret = ECGOTHER;
×
3202
                }
3203
        }
3204
        free(dir);
188✔
3205

3206
        return ret;
188✔
3207
}
3208

3209
/*
3210
 * Checks if the cgroup's controller shares the mount point with any other
3211
 * controller in cg_mount_table.
3212
 * Returns 1 if shared or 0.
3213
 */
3214
static int is_cgrp_ctrl_shared_mnt(char *controller)
193✔
3215
{
3216
        int ret = 0;
193✔
3217
        int i;
3218

3219
        if (!controller)
193✔
3220
                return ret;
22✔
3221

3222
        pthread_rwlock_rdlock(&cg_mount_table_lock);
171✔
3223

3224
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
395✔
3225

3226
                if (strncmp(cg_mount_table[i].name, controller, CONTROL_NAMELEN_MAX) == 0 &&
395✔
3227
                    cg_mount_table[i].shared_mnt) {
171✔
3228
                        ret = 1;
171✔
3229
                        break;
171✔
3230
                }
3231
        }
3232

3233
        pthread_rwlock_unlock(&cg_mount_table_lock);
171✔
3234

3235
        return ret;
171✔
3236
}
3237
/**
3238
 * Find the parent of the specified directory. It returns the parent in
3239
 * hierarchy of given controller (the parent is usually name/.. unless name
3240
 * is a mount point.  It is assumed both the cgroup (and, therefore, parent)
3241
 * already exist, and will fail otherwise.
3242
 *
3243
 * When namespaces are used, a group can have different parents for
3244
 * different controllers.
3245
 *
3246
 * @param cgrp The cgroup
3247
 * @param controller The controller
3248
 * @param parent Output, name of parent's group (if the group has parent) or
3249
 *        NULL, if the provided cgroup is the root group and has no parent.
3250
 *        Caller is responsible to free the returned string!
3251
 * @return 0 on success, >0 on error.
3252
 */
3253
static int cgroup_find_parent(struct cgroup *cgrp, char *controller, char **parent)
193✔
3254
{
3255
        struct stat stat_child, stat_parent;
3256
        char child_path[FILENAME_MAX];
3257
        char *parent_path = NULL;
193✔
3258
        int ret = 0;
193✔
3259

3260
        *parent = NULL;
193✔
3261

3262
        pthread_rwlock_rdlock(&cg_mount_table_lock);
193✔
3263
        if (!cg_build_path_locked(cgrp->name, child_path, controller)) {
193✔
3264
                pthread_rwlock_unlock(&cg_mount_table_lock);
×
3265
                return ECGFAIL;
×
3266
        }
3267
        pthread_rwlock_unlock(&cg_mount_table_lock);
193✔
3268

3269
        cgroup_dbg("path is %s\n", child_path);
193✔
3270

3271
        if (asprintf(&parent_path, "%s/..", child_path) < 0)
193✔
3272
                return ECGFAIL;
×
3273

3274
        cgroup_dbg("parent's name is %s\n", parent_path);
193✔
3275

3276
        if (stat(child_path, &stat_child) < 0) {
193✔
3277
                if (is_cgrp_ctrl_shared_mnt(controller)) {
5✔
3278
                        last_errno = errno;
5✔
3279
                        ret = ECGROUPNOTEXIST;
5✔
3280
                        goto free_parent;
5✔
3281
                }
3282
                last_errno = errno;
×
3283
                ret = ECGOTHER;
×
3284
                goto free_parent;
×
3285
        }
3286

3287
        if (stat(parent_path, &stat_parent) < 0) {
188✔
3288
                last_errno = errno;
×
3289
                ret = ECGOTHER;
×
3290
                goto free_parent;
×
3291
        }
3292

3293
        /* Is the specified "name" a mount point? */
3294
        if (stat_parent.st_dev != stat_child.st_dev) {
188✔
3295
                *parent = NULL;
×
3296
                ret = 0;
×
3297
                cgroup_dbg("Parent is on different device\n");
×
3298
        } else {
3299
                ret = cgroup_get_parent_name(cgrp, parent);
188✔
3300
        }
3301

3302
free_parent:
193✔
3303
        free(parent_path);
193✔
3304
        return ret;
193✔
3305
}
3306

3307
/**
3308
 * @cgrp: cgroup data structure to be filled with parent values and then
3309
 *        passed down for creation
3310
 * @ignore_ownership: Ignore doing a chown on the newly created cgroup
3311
 * @return 0 on success, > 0 on failure.  If ECGCANTSETVALUE is returned,
3312
 *        the group was created
3313
 * successfully, but not all controller parameters were copied from the
3314
 * parent successfully; unfortunately, this is expected...
3315
 */
3316
int cgroup_create_cgroup_from_parent(struct cgroup *cgrp, int ignore_ownership)
×
3317
{
3318
        struct cgroup *parent_cgrp = NULL;
×
3319
        char *parent = NULL;
×
3320
        int ret = ECGFAIL;
×
3321

3322
        if (!cgroup_initialized)
×
3323
                return ECGROUPNOTINITIALIZED;
×
3324

3325
        ret = cgroup_get_parent_name(cgrp, &parent);
×
3326
        if (ret)
×
3327
                return ret;
×
3328

3329
        if (parent == NULL) {
×
3330
                /*
3331
                 * The group to create is root group!
3332
                 * TODO: find better error code?
3333
                 */
3334
                return ECGFAIL;
×
3335
        }
3336

3337
        cgroup_dbg("parent is %s\n", parent);
×
3338
        parent_cgrp = cgroup_new_cgroup(parent);
×
3339
        if (!parent_cgrp) {
×
3340
                ret = ECGFAIL;
×
3341
                goto err_nomem;
×
3342
        }
3343

3344
        if (cgroup_get_cgroup(parent_cgrp)) {
×
3345
                ret = ECGFAIL;
×
3346
                goto err_parent;
×
3347
        }
3348

3349
        cgroup_dbg("got parent group for %s\n", parent_cgrp->name);
×
3350
        ret = cgroup_copy_cgroup(cgrp, parent_cgrp);
×
3351
        if (ret)
×
3352
                goto err_parent;
×
3353

3354
        cgroup_dbg("copied parent group %s to %s\n", parent_cgrp->name, cgrp->name);
×
3355
        ret = cgroup_create_cgroup(cgrp, ignore_ownership);
×
3356

3357
err_parent:
×
3358
        cgroup_free(&parent_cgrp);
×
3359
err_nomem:
×
3360
        free(parent);
×
3361
        return ret;
×
3362
}
3363

3364
/**
3365
 * Move all processes from one task file to another.
3366
 * @param input_tasks Pre-opened file to read tasks from.
3367
 * @param output_tasks Pre-opened file to write tasks to.
3368
 * @return 0 on success, >0 on error.
3369
 */
3370
static int cg_move_task_files(FILE *input_tasks, FILE *output_tasks)
220✔
3371
{
3372
        int ret = 0;
220✔
3373
        int tids;
3374

3375
        while (!feof(input_tasks)) {
240✔
3376
                ret = fscanf(input_tasks, "%d", &tids);
240✔
3377
                if (ret == EOF || ret == 0) {
240✔
3378
                        ret = 0;
220✔
3379
                        break;
220✔
3380
                }
3381
                if (ret < 0)
20✔
3382
                        break;
×
3383

3384
                ret = fprintf(output_tasks, "%d", tids);
20✔
3385
                if (ret < 0 && errno != ESRCH)
20✔
3386
                        break;
×
3387

3388
                /* Flush the file, we need only one process per write() call. */
3389
                ret = fflush(output_tasks);
20✔
3390
                if (ret < 0) {
20✔
3391
                        if (errno == ESRCH)
×
3392
                                ret = 0;
×
3393
                        else
3394
                                break;
×
3395
                }
3396
        }
3397

3398
        if (ret < 0) {
220✔
3399
                last_errno = errno;
×
3400
                return ECGOTHER;
×
3401
        }
3402
        return 0;
220✔
3403
}
3404

3405
/**
3406
 * Remove one cgroup from specific controller. The function moves all
3407
 * processes from it to given target group.
3408
 *
3409
 * The function succeeds if the group to remove is already removed - when
3410
 * cgroup_delete_cgroup is called with group with two controllers mounted
3411
 * to the same hierarchy, this function is called once for each of these
3412
 * controllers. And during the second call the group is already removed...
3413
 *
3414
 * @param cgrp_name Name of the group to remove.
3415
 * @param controller  Name of the controller.
3416
 * @param target_tasks Opened tasks file of the target group, where all
3417
 *        processes should be moved.
3418
 * @param flags Flag indicating whether the errors from task
3419
 *        migration should be ignored (CGROUP_DELETE_IGNORE_MIGRATION) or not (0).
3420
 * @returns 0 on success, >0 on error.
3421
 */
3422
static int cg_delete_cgrp_controller(char *cgrp_name, char *controller, FILE *target_tasks,
220✔
3423
                                     int flags)
3424
{
3425
        char path[FILENAME_MAX];
3426
        FILE *delete_tasks;
3427
        int ret = 0;
220✔
3428

3429
        cgroup_dbg("Removing group %s:%s\n", controller, cgrp_name);
220✔
3430

3431
        if (!(flags & CGFLAG_DELETE_EMPTY_ONLY)) {
220✔
3432
                /* Open tasks file of the group to delete. */
3433
                ret = cgroup_build_tasks_procs_path(path, sizeof(path), cgrp_name, controller);
220✔
3434
                if (ret != 0)
220✔
3435
                        return ECGROUPSUBSYSNOTMOUNTED;
×
3436

3437
                delete_tasks = fopen(path, "re");
220✔
3438
                if (delete_tasks) {
220✔
3439
                        ret = cg_move_task_files(delete_tasks, target_tasks);
220✔
3440
                        if (ret != 0) {
220✔
3441
                                cgroup_warn("removing tasks from %s failed: %s\n", path,
×
3442
                                            cgroup_strerror(ret));
3443
                        }
3444
                        fclose(delete_tasks);
220✔
3445
                } else {
3446
                        /*
3447
                         * Can't open the tasks file. If the file does not exist,
3448
                         * ignore it - the group has been already removed.
3449
                         */
3450
                        if (errno != ENOENT) {
×
3451
                                cgroup_err("cannot open %s: %s\n", path, strerror(errno));
×
3452
                                last_errno = errno;
×
3453
                                ret = ECGOTHER;
×
3454
                        }
3455
                }
3456

3457
                if (ret != 0 && !(flags & CGFLAG_DELETE_IGNORE_MIGRATION))
220✔
3458
                        return ret;
×
3459
        }
3460

3461
        /* Remove the group. */
3462
        if (!cg_build_path(cgrp_name, path, controller))
220✔
3463
                return ECGROUPSUBSYSNOTMOUNTED;
×
3464

3465
        ret = rmdir(path);
220✔
3466
        if (ret == 0 || errno == ENOENT)
220✔
3467
                return 0;
220✔
3468

3469
        if ((flags & CGFLAG_DELETE_EMPTY_ONLY) && (errno == EBUSY))
×
3470
                return ECGNONEMPTY;
×
3471

3472
        cgroup_warn("cannot remove directory %s: %s\n", path, strerror(errno));
×
3473
        last_errno = errno;
×
3474

3475
        return ECGOTHER;
×
3476
}
3477

3478
/**
3479
 * Recursively delete one control group. Moves all tasks from the group and
3480
 * its subgroups to given task file.
3481
 *
3482
 * @param cgrp_name The group to delete.
3483
 * @param controller The controller, where to delete.
3484
 * @param target_tasks Opened file, where all tasks should be moved.
3485
 * @param flags Combination of CGFLAG_DELETE_* flags. The function assumes
3486
 *        that CGFLAG_DELETE_RECURSIVE is set.
3487
 * @param delete_root Whether the group itself should be removed(1) or not(0).
3488
 */
3489
static int cg_delete_cgrp_controller_recursive(char *cgrp_name, char *controller,
21✔
3490
                                               FILE *target_tasks, int flags, int delete_root)
3491
{
3492
        char child_name[FILENAME_MAX + 1];
3493
        struct cgroup_file_info info;
3494
        int level, group_len;
3495
        void *handle;
3496
        int ret;
3497

3498
        cgroup_dbg("Recursively removing %s:%s\n", controller, cgrp_name);
21✔
3499

3500
        ret = cgroup_walk_tree_begin(controller, cgrp_name, 0, &handle, &info, &level);
21✔
3501
        if (ret == 0)
21✔
3502
                ret = cgroup_walk_tree_set_flags(&handle, CGROUP_WALK_TYPE_POST_DIR);
21✔
3503

3504
        if (ret != 0) {
21✔
3505
                cgroup_walk_tree_end(&handle);
×
3506
                return ret;
×
3507
        }
3508

3509
        group_len = strlen(info.full_path);
21✔
3510

3511
        /* Skip the root group, it will be handled explicitly at the end. */
3512
        ret = cgroup_walk_tree_next(0, &handle, &info, level);
21✔
3513

3514
        while (ret == 0) {
2,500✔
3515
                if (info.type == CGROUP_FILE_TYPE_DIR && info.depth > 0) {
2,479✔
3516
                        snprintf(child_name, sizeof(child_name), "%s/%s", cgrp_name,
32✔
3517
                                 info.full_path + group_len);
32✔
3518

3519
                        ret = cg_delete_cgrp_controller(child_name, controller, target_tasks,
32✔
3520
                                                          flags);
3521
                        if (ret != 0)
32✔
3522
                                break;
×
3523
                }
3524

3525
                ret = cgroup_walk_tree_next(0, &handle, &info, level);
2,479✔
3526
        }
3527
        if (ret == ECGEOF) {
21✔
3528
                /* Iteration finished successfully, remove the root group. */
3529
                ret = 0;
21✔
3530
                if (delete_root)
21✔
3531
                        ret = cg_delete_cgrp_controller(cgrp_name, controller, target_tasks, flags);
21✔
3532
        }
3533

3534
        cgroup_walk_tree_end(&handle);
21✔
3535

3536
        return ret;
21✔
3537
}
3538

3539
/**
3540
 * cgroup_delete cgroup deletes a control group.
3541
 * struct cgroup *cgrp takes the group which is to be deleted.
3542
 *
3543
 * returns 0 on success.
3544
 */
3545
int cgroup_delete_cgroup(struct cgroup *cgrp, int ignore_migration)
27✔
3546
{
3547
        int flags = ignore_migration ? CGFLAG_DELETE_IGNORE_MIGRATION : 0;
27✔
3548

3549
        return cgroup_delete_cgroup_ext(cgrp, flags);
27✔
3550
}
3551

3552
int cgroup_delete_cgroup_ext(struct cgroup *cgrp, int flags)
192✔
3553
{
3554
        int first_error = 0, first_errno = 0;
192✔
3555
        int cgrp_del_on_shared_mnt = 0;
192✔
3556
        char parent_path[FILENAME_MAX];
3557
        char *controller_name = NULL;
192✔
3558
        FILE *parent_tasks = NULL;
192✔
3559
        char *parent_name = NULL;
192✔
3560
        int delete_group = 1;
192✔
3561
        int empty_cgrp = 0;
192✔
3562
        int i, ret;
3563

3564
        if (!cgroup_initialized)
192✔
3565
                return ECGROUPNOTINITIALIZED;
×
3566

3567
        if (!cgrp)
192✔
3568
                return ECGROUPNOTALLOWED;
×
3569

3570
        if ((flags & CGFLAG_DELETE_RECURSIVE)
192✔
3571
            && (flags & CGFLAG_DELETE_EMPTY_ONLY))
25✔
3572
                return ECGINVAL;
×
3573

3574
        if (cgrp->index == 0)
192✔
3575
                /* Valid empty cgroup v2 with not controllers added. */
3576
                empty_cgrp = 1;
22✔
3577

3578
        for (i = 0; i < cgrp->index; i++) {
363✔
3579
                if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name))
171✔
3580
                        return ECGROUPSUBSYSNOTMOUNTED;
×
3581
        }
3582

3583
        /*
3584
         * Remove the group from all controllers and in the case of cgroup
3585
         * with no controllers, perform all actions of a single controller.
3586
         */
3587
        for (i = 0; empty_cgrp > 0 || i < cgrp->index; i++, empty_cgrp--) {
385✔
3588

3589
                ret = 0;
193✔
3590
                controller_name = NULL;
193✔
3591

3592
                if (cgrp->controller[i])
193✔
3593
                        controller_name = cgrp->controller[i]->name;
171✔
3594

3595
                /* Find parent, it can be different for each controller */
3596
                if (!(flags & CGFLAG_DELETE_EMPTY_ONLY)) {
193✔
3597
                        ret = cgroup_find_parent(cgrp, controller_name, &parent_name);
193✔
3598
                        if (ret) {
193✔
3599
                                /*
3600
                                 * ECGROPNOTEXIST is returned on cgroup v1, where
3601
                                 * controllers share the mount points. When cgroup
3602
                                 * is deleted on one of symbolic link (controller)
3603
                                 * and they should pass on other controllers sharing
3604
                                 * the mount point.
3605
                                 *
3606
                                 * cgrp_del_shared_mnt serves as an extra check
3607
                                 * flag that gets set, when the cgroup exists and
3608
                                 * is deleted or else sets an error.
3609
                                 */
3610
                                if (first_error == 0 &&
5✔
3611
                                    (ret != ECGROUPNOTEXIST ||
5✔
3612
                                    (ret == ECGROUPNOTEXIST && cgrp_del_on_shared_mnt == 0))) {
5✔
3613
                                        first_errno = last_errno;
4✔
3614
                                        first_error = ECGOTHER;
4✔
3615
                                }
3616
                                continue;
5✔
3617
                        }
3618

3619
                        if (is_cgrp_ctrl_shared_mnt(controller_name))
188✔
3620
                                cgrp_del_on_shared_mnt = 1;
166✔
3621

3622
                        if (parent_name == NULL) {
188✔
3623
                                /* Root group is being deleted. */
3624
                                if (!(flags & CGFLAG_DELETE_RECURSIVE))
×
3625
                                        /* root group is being deleted in non-recursive mode */
3626
                                        continue;
×
3627
                                /*
3628
                                 * Move all tasks to the root group and
3629
                                 * do not delete it afterwards.
3630
                                 */
3631
                                parent_name = strdup(".");
×
3632
                                if (parent_name == NULL) {
×
3633
                                        if (first_error == 0) {
×
3634
                                                first_errno = errno;
×
3635
                                                first_error = ECGOTHER;
×
3636
                                        }
3637
                                        continue;
×
3638
                                }
3639
                                delete_group = 0;
×
3640
                        }
3641
                }
3642

3643
                if (parent_name) {
188✔
3644
                        /* Tasks need to be moved, pre-open target tasks file */
3645
                        ret = cgroup_build_tasks_procs_path(parent_path, sizeof(parent_path),
188✔
3646
                                                            parent_name, controller_name);
3647
                        if (ret != 0) {
188✔
3648
                                if (first_error == 0)
×
3649
                                        first_error = ECGFAIL;
×
3650
                                free(parent_name);
×
3651
                                continue;
×
3652
                        }
3653

3654
                        parent_tasks = fopen(parent_path, "we");
188✔
3655
                        if (!parent_tasks) {
188✔
3656
                                if (first_error == 0) {
×
3657
                                        cgroup_warn("cannot open tasks file %s: %s\n", parent_path,
×
3658
                                                    strerror(errno));
3659
                                        first_errno = errno;
×
3660
                                        first_error = ECGOTHER;
×
3661
                                }
3662
                                free(parent_name);
×
3663
                                continue;
×
3664
                        }
3665
                }
3666
                if (flags & CGFLAG_DELETE_RECURSIVE) {
188✔
3667
                        ret = cg_delete_cgrp_controller_recursive(cgrp->name, controller_name,
21✔
3668
                                                                    parent_tasks, flags,
3669
                                                                    delete_group);
3670
                } else {
3671
                        ret = cg_delete_cgrp_controller(cgrp->name, controller_name, parent_tasks,
167✔
3672
                                                        flags);
3673
                }
3674

3675
                if (parent_tasks) {
188✔
3676
                        fclose(parent_tasks);
188✔
3677
                        parent_tasks = NULL;
188✔
3678
                }
3679
                free(parent_name);
188✔
3680
                parent_name = NULL;
188✔
3681
                /*
3682
                 * If any of the controller delete fails, remember the first
3683
                 * error code, but continue with next controller and try remove
3684
                 * the group from all of them.
3685
                 */
3686
                if (ret) {
188✔
3687
                        /*
3688
                         * ECGNONEMPTY is more or less not an error, but an
3689
                         * indication that something was not removed.
3690
                         * Therefore it should be replaced by any other error.
3691
                         */
3692
                        if (ret != ECGNONEMPTY &&
×
3693
                            (first_error == 0 || first_errno == ECGNONEMPTY)) {
×
3694
                                first_errno = last_errno;
×
3695
                                first_error = ret;
×
3696
                        }
3697
                }
3698
        }
3699

3700
        /*
3701
         * Restore the last_errno to the first errno from
3702
         * cg_delete_cgroup_controller[_ext].
3703
         */
3704
        if (first_errno != 0)
192✔
3705
                last_errno = first_errno;
4✔
3706

3707
        return first_error;
192✔
3708
}
3709

3710
/*
3711
 * This function should really have more checks, but this version will assume
3712
 * that the callers have taken care of everything. Including the locking.
3713
 */
3714
static int cg_rd_ctrl_file(const char *subsys, const char *cgrp, const char *file, char **value)
3,295✔
3715
{
3716
        char path[FILENAME_MAX];
3717
        FILE *ctrl_file = NULL;
3,295✔
3718
        int ret;
3719

3720
        if (!cg_build_path_locked(cgrp, path, subsys))
3,295✔
3721
                return ECGFAIL;
×
3722

3723
        strncat(path, file, sizeof(path) - strlen(path));
3,295✔
3724
        ctrl_file = fopen(path, "re");
3,295✔
3725
        if (!ctrl_file)
3,295✔
3726
                return ECGROUPVALUENOTEXIST;
10✔
3727

3728
        *value = calloc(CG_CONTROL_VALUE_MAX, 1);
3,285✔
3729
        if (!*value) {
3,285✔
3730
                fclose(ctrl_file);
×
3731
                last_errno = errno;
×
3732
                return ECGOTHER;
×
3733
        }
3734

3735
        /* Using %as crashes when we try to read from files like memory.stat */
3736
        ret = fread(*value, 1, CG_CONTROL_VALUE_MAX-1, ctrl_file);
3,285✔
3737
        if (ret < 0) {
3,285✔
3738
                free(*value);
×
3739
                *value = NULL;
×
3740
        } else {
3741
                /* Remove trailing \n */
3742
                if (ret > 0 && (*value)[ret-1] == '\n')
3,285✔
3743
                        (*value)[ret-1] = '\0';
3,067✔
3744
        }
3745

3746
        fclose(ctrl_file);
3,285✔
3747

3748
        return 0;
3,285✔
3749
}
3750

3751
/*
3752
 * Call this function with required locks taken.
3753
 */
3754
int cgroup_fill_cgc(struct dirent *ctrl_dir, struct cgroup *cgrp, struct cgroup_controller *cgc,
20,971✔
3755
                    int cg_index)
3756
{
3757
        char path[FILENAME_MAX+1];
3758
        struct stat stat_buffer;
3759
        char *ctrl_value = NULL;
20,971✔
3760
        char *ctrl_name = NULL;
20,971✔
3761
        char *ctrl_file = NULL;
20,971✔
3762
        char *tmp_path = NULL;
20,971✔
3763
        char *d_name = NULL;
20,971✔
3764
        char *buffer = NULL;
20,971✔
3765
        int tmp_len = 0;
20,971✔
3766
        int error = 0;
20,971✔
3767

3768
        d_name = strdup(ctrl_dir->d_name);
20,971✔
3769

3770
        if (!strcmp(d_name, ".") || !strcmp(d_name, "..")) {
20,971✔
3771
                error = ECGINVAL;
×
3772
                goto fill_error;
×
3773
        }
3774

3775
        /*
3776
         * This part really needs to be optimized out. Probably use some
3777
         * sort of a flag, but this is fine for now.
3778
         */
3779
        cg_build_path_locked(cgrp->name, path, cg_mount_table[cg_index].name);
20,971✔
3780
        strncat(path, d_name, sizeof(path) - strlen(path));
20,971✔
3781

3782
        error = stat(path, &stat_buffer);
20,971✔
3783
        if (error) {
20,971✔
3784
                error = ECGFAIL;
×
3785
                goto fill_error;
×
3786
        }
3787

3788
        /*
3789
         * We have already stored the tasks_uid & tasks_gid. This check is
3790
         * to avoid the overwriting of the values stored in
3791
         * control_uid & cotrol_gid. tasks file will have the uid and gid of
3792
         * the user who is capable of putting a task to this cgroup.
3793
         * control_uid and control_gid is meant for the users who are capable
3794
         * of managing the cgroup shares.
3795
         *
3796
         * The strstr() function will return the pointer to the
3797
         * beginning of the sub string "/tasks".
3798
         */
3799
        tmp_len = strlen(path) - strlen("/tasks");
20,971✔
3800

3801
        /* tmp_path would be pointing to the last six characters */
3802
        tmp_path = (char *)path + tmp_len;
20,971✔
3803

3804
        /*
3805
         * Checking to see, if this is actually a 'tasks' file We need to
3806
         * compare the last 6 bytes
3807
         */
3808
        if (strcmp(tmp_path, "/tasks")) {
20,971✔
3809
                cgrp->control_uid = stat_buffer.st_uid;
20,962✔
3810
                cgrp->control_gid = stat_buffer.st_gid;
20,962✔
3811
        }
3812

3813
        ctrl_name = strtok_r(d_name, ".", &buffer);
20,971✔
3814
        if (!ctrl_name) {
20,971✔
3815
                error = ECGFAIL;
×
3816
                goto fill_error;
×
3817
        }
3818

3819
        ctrl_file = strtok_r(NULL, ".", &buffer);
20,971✔
3820
        if (!ctrl_file) {
20,971✔
3821
                error = ECGINVAL;
9✔
3822
                goto fill_error;
9✔
3823
        }
3824

3825
        if (strcmp(ctrl_name, cg_mount_table[cg_index].name) == 0) {
20,962✔
3826
                error = cg_rd_ctrl_file(cg_mount_table[cg_index].name, cgrp->name,
3,295✔
3827
                                        ctrl_dir->d_name, &ctrl_value);
3,295✔
3828
                if (error || !ctrl_value)
3,295✔
3829
                        goto fill_error;
10✔
3830

3831
                if (cgroup_add_value_string(cgc, ctrl_dir->d_name, ctrl_value)) {
3,285✔
3832
                        error = ECGFAIL;
×
3833
                        goto fill_error;
×
3834
                }
3835
        }
3836
fill_error:
20,952✔
3837
        if (ctrl_value)
20,971✔
3838
                free(ctrl_value);
3,285✔
3839
        free(d_name);
20,971✔
3840

3841
        return error;
20,971✔
3842
}
3843

3844
/*
3845
 * cgroup_get_cgroup reads the cgroup data from the filesystem.
3846
 * struct cgroup has the name of the group to be populated
3847
 *
3848
 * return 0 on success.
3849
 */
3850
int cgroup_get_cgroup(struct cgroup *cgrp)
70✔
3851
{
3852
        char cgrp_ctrl_path[FILENAME_MAX];
3853
        struct dirent *ctrl_dir = NULL;
70✔
3854
        char mnt_path[FILENAME_MAX];
3855
        int initial_controller_cnt;
3856
        char *control_path = NULL;
70✔
3857
        int controller_cnt = 0;
70✔
3858
        DIR *dir = NULL;
70✔
3859
        int error;
3860
        int i, j;
3861
        int ret;
3862

3863
        if (!cgroup_initialized) {
70✔
3864
                /* ECGROUPNOTINITIALIZED */
3865
                return ECGROUPNOTINITIALIZED;
×
3866
        }
3867

3868
        if (!cgrp) {
70✔
3869
                /* ECGROUPNOTALLOWED */
3870
                return ECGROUPNOTALLOWED;
×
3871
        }
3872

3873
        initial_controller_cnt = cgrp->index;
70✔
3874

3875
        pthread_rwlock_rdlock(&cg_mount_table_lock);
70✔
3876
        for (i = 0; i < CG_CONTROLLER_MAX && cg_mount_table[i].name[0] != '\0'; i++) {
693✔
3877
                struct cgroup_controller *cgc;
3878
                struct stat stat_buffer;
3879
                int mnt_path_len;
3880

3881
                if (initial_controller_cnt > 0) {
624✔
3882
                        bool skip_this_controller = true;
54✔
3883

3884
                        /*
3885
                         * The user has specified a list of controllers they are interested
3886
                         * in.  Only operate on the specified controllers
3887
                         */
3888
                        for (j = 0; j < cgrp->index; j++) {
198✔
3889
                                if (strncmp(cg_mount_table[i].name, cgrp->controller[j]->name,
144✔
3890
                                            CONTROL_NAMELEN_MAX) == 0)
3891
                                        skip_this_controller = false;
16✔
3892
                        }
3893

3894
                        if (skip_this_controller)
54✔
3895
                                continue;
302✔
3896
                }
3897

3898
                if (!cg_build_path_locked(NULL, mnt_path, cg_mount_table[i].name))
586✔
3899
                        continue;
×
3900

3901
                mnt_path_len = strlen(mnt_path);
586✔
3902
                strncat(mnt_path, cgrp->name, FILENAME_MAX - mnt_path_len - 1);
586✔
3903
                mnt_path[sizeof(mnt_path) - 1] = '\0';
586✔
3904

3905
                if (access(mnt_path, F_OK))
586✔
3906
                        continue;
2✔
3907

3908
                if (!cg_build_path_locked(cgrp->name, cgrp_ctrl_path, cg_mount_table[i].name)) {
584✔
3909
                        /* This fails when the cgroup does not exist for that controller. */
3910
                        continue;
×
3911
                }
3912

3913
                /* Get the uid and gid information. */
3914
                if (cg_mount_table[i].version == CGROUP_V1) {
584✔
3915
                        ret = asprintf(&control_path, "%s/tasks", cgrp_ctrl_path);
10✔
3916

3917
                        if (ret < 0) {
10✔
3918
                                last_errno = errno;
×
3919
                                error = ECGOTHER;
×
3920
                                goto unlock_error;
1✔
3921
                        }
3922

3923
                        if (stat(control_path, &stat_buffer)) {
10✔
3924
                                last_errno = errno;
1✔
3925
                                free(control_path);
1✔
3926
                                error = ECGOTHER;
1✔
3927
                                goto unlock_error;
1✔
3928
                        }
3929

3930
                        cgrp->tasks_uid = stat_buffer.st_uid;
9✔
3931
                        cgrp->tasks_gid = stat_buffer.st_gid;
9✔
3932

3933
                        free(control_path);
9✔
3934
                } else { /* cgroup v2 */
3935
                        bool enabled;
3936

3937
                        error = cgroupv2_get_controllers(cgrp_ctrl_path, cg_mount_table[i].name,
574✔
3938
                                                         &enabled);
3939
                        if (error == ECGROUPNOTMOUNTED) {
574✔
3940
                                /*
3941
                                 * This controller isn't enabled.  Only hide it from the
3942
                                 * user if they've chosen to view all enabled controllers.
3943
                                 *
3944
                                 * If they've specified the controllers they're interested in
3945
                                 * and we've made it this far, then they are explicitly
3946
                                 * interested in this controller and we should not remove it.
3947
                                 */
3948
                                if (initial_controller_cnt == 0) {
266✔
3949
                                        controller_cnt++;
262✔
3950
                                        continue;
262✔
3951
                                }
3952
                        } else if (error) {
308✔
3953
                                goto unlock_error;
×
3954
                        }
3955
                }
3956

3957
                if (initial_controller_cnt)
321✔
3958
                        cgc = cgroup_get_controller(cgrp, cg_mount_table[i].name);
16✔
3959
                else
3960
                        cgc = cgroup_add_controller(cgrp, cg_mount_table[i].name);
305✔
3961
                if (!cgc) {
321✔
3962
                        error = ECGINVAL;
×
3963
                        goto unlock_error;
×
3964
                }
3965

3966
                dir = opendir(cgrp_ctrl_path);
321✔
3967
                if (!dir) {
321✔
3968
                        last_errno = errno;
×
3969
                        error = ECGOTHER;
×
3970
                        goto unlock_error;
×
3971
                }
3972

3973
                controller_cnt++;
321✔
3974

3975
                while ((ctrl_dir = readdir(dir)) != NULL) {
19,380✔
3976
                        /* Skip over non regular files */
3977
                        if (ctrl_dir->d_type != DT_REG)
19,059✔
3978
                                continue;
872✔
3979

3980
                        error = cgroup_fill_cgc(ctrl_dir, cgrp, cgc, i);
18,187✔
3981
                        for (j = 0; j < cgc->index; j++)
106,528✔
3982
                                cgc->values[j]->dirty = false;
88,341✔
3983

3984
                        if (error == ECGFAIL) {
18,187✔
3985
                                closedir(dir);
×
3986
                                goto unlock_error;
×
3987
                        }
3988
                }
3989
                closedir(dir);
321✔
3990

3991
                if (!strcmp(cgc->name, "memory")) {
321✔
3992
                        /*
3993
                         * Make sure that memory.limit_in_bytes is placed before
3994
                         * memory.memsw.limit_in_bytes in the list of values
3995
                         */
3996
                        int memsw_limit = -1;
66✔
3997
                        int mem_limit = -1;
66✔
3998

3999
                        for (j = 0; j < cgc->index; j++) {
1,406✔
4000
                                if (!strcmp(cgc->values[j]->name, "memory.memsw.limit_in_bytes"))
1,340✔
4001
                                        memsw_limit = j;
2✔
4002
                                else if (!strcmp(cgc->values[j]->name, "memory.limit_in_bytes"))
1,338✔
4003
                                        mem_limit = j;
2✔
4004
                        }
4005

4006
                        if (memsw_limit >= 0 && memsw_limit < mem_limit) {
66✔
4007
                                struct control_value *val = cgc->values[memsw_limit];
×
4008

4009
                                cgc->values[memsw_limit] = cgc->values[mem_limit];
×
4010
                                cgc->values[mem_limit] = val;
×
4011
                        }
4012
                }
4013
        }
4014

4015
        /*
4016
         * Check if the group really exists or not.  The cgrp->index controller count can't
4017
         * be used in this case because cgroup v2 allows controllers to be enabled/disabled in
4018
         * the subtree_control file.  Rather, cgroup_get_cgroup() tracks the number of possible
4019
         * controllers in the controller_cnt variable and uses that to determine if the cgroup
4020
         * exists or not.
4021
         */
4022
        if (!controller_cnt) {
69✔
4023
                error = ECGROUPNOTEXIST;
×
4024
                goto unlock_error;
×
4025
        }
4026

4027
        pthread_rwlock_unlock(&cg_mount_table_lock);
69✔
4028

4029
        return 0;
69✔
4030

4031
unlock_error:
1✔
4032
        pthread_rwlock_unlock(&cg_mount_table_lock);
1✔
4033
        /*
4034
         * XX: Need to figure out how to cleanup? Cleanup just the stuff
4035
         * we added, or the whole structure.
4036
         */
4037
        cgroup_free_controllers(cgrp);
1✔
4038
        cgrp = NULL;
1✔
4039

4040
        return error;
1✔
4041
}
4042

4043
/**
4044
 * cg_prepare_cgroup Process the selected rule. Prepare the cgroup structure
4045
 * which can be used to add the task to destination cgroup.
4046
 *
4047
 *  returns 0 on success.
4048
 */
4049
static int cg_prepare_cgroup(struct cgroup *cgrp, pid_t pid, const char *dest,
48✔
4050
                             const char * const controllers[])
4051
{
4052
        struct cgroup_controller *cptr = NULL;
48✔
4053
        const char *controller = NULL;
48✔
4054
        int ret = 0, i;
48✔
4055

4056
        /* Fill in cgroup details.  */
4057
        cgroup_dbg("Will move pid %d to cgroup '%s'\n", pid, dest);
48✔
4058

4059
        strncpy(cgrp->name, dest, FILENAME_MAX);
48✔
4060
        cgrp->name[FILENAME_MAX-1] = '\0';
48✔
4061

4062
        /* Scan all the controllers */
4063
        for (i = 0; i < CG_CONTROLLER_MAX; i++) {
111✔
4064
                int j = 0;
111✔
4065

4066
                if (!controllers[i])
111✔
4067
                        return 0;
48✔
4068
                controller = controllers[i];
63✔
4069

4070
                /* If first string is "*" that means all the mounted controllers. */
4071
                if (strcmp(controller, "*") == 0) {
63✔
4072
                        pthread_rwlock_rdlock(&cg_mount_table_lock);
×
4073

4074
                        for (j = 0; j < CG_CONTROLLER_MAX &&
×
4075
                                cg_mount_table[j].name[0] != '\0'; j++) {
×
4076
                                cgroup_dbg("Adding controller %s\n", cg_mount_table[j].name);
×
4077
                                cptr = cgroup_add_controller(cgrp, cg_mount_table[j].name);
×
4078
                                if (!cptr) {
×
4079
                                        cgroup_warn("adding controller '%s' failed\n",
×
4080
                                                    cg_mount_table[j].name);
4081
                                        pthread_rwlock_unlock(&cg_mount_table_lock);
×
4082
                                        cgroup_free_controllers(cgrp);
×
4083
                                        return ECGROUPNOTALLOWED;
×
4084
                                }
4085
                        }
4086
                        pthread_rwlock_unlock(&cg_mount_table_lock);
×
4087
                        return ret;
×
4088
                }
4089

4090
                /* It is individual controller names and not "*" */
4091
                cgroup_dbg("Adding controller %s\n", controller);
63✔
4092
                cptr = cgroup_add_controller(cgrp, controller);
63✔
4093
                if (!cptr) {
63✔
4094
                        cgroup_warn("adding controller '%s' failed\n", controller);
×
4095
                        cgroup_free_controllers(cgrp);
×
4096
                        return ECGROUPNOTALLOWED;
×
4097
                }
4098
        }
4099

4100
        return ret;
×
4101
}
4102

4103
/**
4104
 * Determines if the rule is a wildcard rule and if so, compares the wildcard
4105
 * rule against the new process.  If the new process matches the wildcard rule,
4106
 * then this function returns true. Otherwise it returns false.
4107
 *
4108
 *        @param rule_procname The procname field of the rule
4109
 *        @param procname The name of the new process
4110
 *        @return True if the procname matches the rule.  False otherwise
4111
 */
4112
STATIC bool cgroup_compare_wildcard_procname(const char * const rule_procname,
427✔
4113
                                             const char * const procname)
4114
{
4115
        size_t rule_strlen = strlen(rule_procname);
427✔
4116

4117
        if (rule_procname[rule_strlen - 1] != '*')
427✔
4118
                /* This rule does not end in a wildcard */
4119
                return false;
420✔
4120

4121
        /* Compare the two strings up to the asterisk */
4122
        if (strncmp(rule_procname, procname, rule_strlen - 1) != 0)
7✔
4123
                /* The strings did not match */
4124
                return false;
3✔
4125

4126
        /* All checks passed.  The wildcarded process matched this rule */
4127
        return true;
4✔
4128
}
4129

4130
static int cgroup_find_matching_destination(char *cgrp_list[], const char * const rule_dest,
14✔
4131
                                            int *matching_index)
4132
{
4133
        size_t rule_strlen = strlen(rule_dest);
14✔
4134
        int ret = -ENODATA;
14✔
4135
        int i;
4136

4137
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
34✔
4138
                if (cgrp_list[i] == NULL)
34✔
4139
                        break;
5✔
4140

4141
                if (rule_dest[rule_strlen - 1] == '/') {
29✔
4142
                        /*
4143
                         * Avoid a weird corner case where given a rule dest
4144
                         * like 'folder/', we _don't_ want to match 'folder1'
4145
                         */
4146
                        if (strlen(cgrp_list[i]) >= rule_strlen &&
7✔
4147
                            cgrp_list[i][rule_strlen - 1] != '/')
6✔
4148
                                continue;
3✔
4149

4150
                        /*
4151
                         * Strip off the '/' at the end of the rule, as
4152
                         * the destination from the cgrp_list will not
4153
                         * have a trailing '/'
4154
                         */
4155
                        rule_strlen--;
4✔
4156
                }
4157

4158
                if (strncmp(rule_dest, cgrp_list[i], rule_strlen) == 0) {
26✔
4159
                        *matching_index = i;
9✔
4160
                        ret = 0;
9✔
4161
                        break;
9✔
4162
                }
4163
        }
4164

4165
        return ret;
14✔
4166
}
4167

4168
static int cgroup_find_matching_controller(char * const *rule_controllers,
10✔
4169
                                           const char * const pid_controller, int *matching_index)
4170
{
4171
        int ret = -ENODATA;
10✔
4172
        int i;
4173

4174
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
11✔
4175
                if (rule_controllers[i] == NULL)
11✔
4176
                        break;
1✔
4177

4178
                if (strlen(rule_controllers[i]) != strlen(pid_controller))
10✔
4179
                        continue;
1✔
4180

4181
                if (strncmp(pid_controller, rule_controllers[i], strlen(pid_controller)) == 0) {
9✔
4182
                        *matching_index = i;
9✔
4183
                        ret = 0;
9✔
4184
                        break;
9✔
4185
                }
4186
        }
4187

4188
        return ret;
10✔
4189
}
4190

4191
static bool cgroup_is_rt_task(const pid_t pid)
14✔
4192
{
4193
        int sched_prio_min, sched_prio_max;
4194
        struct sched_param pid_param;
4195
        int ret;
4196

4197
        ret = sched_getparam(pid, &pid_param);
14✔
4198
        if (ret == -1) {
14✔
4199
                ret = ECGOTHER;
14✔
4200
                last_errno = errno;
14✔
4201
                return false;
14✔
4202
        }
4203

4204
        sched_prio_min = sched_get_priority_min(SCHED_RR);
×
4205
        sched_prio_max = sched_get_priority_max(SCHED_RR);
×
4206

4207
        if (pid_param.sched_priority >= sched_prio_min &&
×
4208
            pid_param.sched_priority <= sched_prio_max)
×
4209
                return true;
×
4210

4211
        return false;
×
4212
}
4213

4214
/**
4215
 * Evaluates if rule is an ignore rule and the pid/procname match this rule.
4216
 * If rule is an ignore rule and the pid/procname match this rule, then this
4217
 * function returns true.  Otherwise it returns false.
4218
 *
4219
 *        @param rule The rule being evaluated
4220
 *        @param pid PID of the process being compared
4221
 *        @param procname Process name of the process being compared
4222
 *        @return True if the rule is an ignore rule and this pid/procname
4223
 *                match the rule.  False otherwise
4224
 */
4225
STATIC bool cgroup_compare_ignore_rule(const struct cgroup_rule * const rule, pid_t pid,
435✔
4226
                                       const char * const procname)
4227
{
4228
        char *controller_list[MAX_MNT_ELEMENTS] = { '\0' };
435✔
4229
        char *cgrp_list[MAX_MNT_ELEMENTS] = { '\0' };
435✔
4230
        int rule_matching_controller_idx;
4231
        int cgrp_list_matching_idx = 0;
435✔
4232
        bool found_match = false;
435✔
4233
        char *token, *saveptr;
4234
        int ret, i;
4235

4236
        if (!rule->is_ignore)
435✔
4237
                /* Immediately return if the 'ignore' option is not set */
4238
                return false;
421✔
4239

4240
        /* If the rule is "ignore", move only non-rt tasks */
4241
        if (rule->is_ignore == CGRULE_OPT_IGNORE && cgroup_is_rt_task(pid) == true)
14✔
4242
                return false;
×
4243
        /* If the rule is "ignore_rt", move only non-rt tasks */
4244
        else if (rule->is_ignore == CGRULE_OPT_IGNORE_RT && cgroup_is_rt_task(pid) == false)
14✔
4245
                return false;
×
4246

4247
        /* If the rule is "ignore" and "ignore_rt", move all tasks */
4248

4249
        ret = cg_get_cgroups_from_proc_cgroups(pid, cgrp_list, controller_list,
14✔
4250
                                               MAX_MNT_ELEMENTS);
4251
        if (ret < 0)
14✔
4252
                goto out;
×
4253

4254
        if (strcmp(rule->destination, "*")) {
14✔
4255
                ret = cgroup_find_matching_destination(cgrp_list, rule->destination,
14✔
4256
                                                       &cgrp_list_matching_idx);
4257
                if (ret < 0)
14✔
4258
                        /* No cgroups matched */
4259
                        goto out;
5✔
4260
        }
4261

4262
        token = strtok_r(controller_list[cgrp_list_matching_idx], ",", &saveptr);
9✔
4263
        while (token != NULL) {
10✔
4264

4265
                ret = cgroup_find_matching_controller(rule->controllers, token,
10✔
4266
                                                      &rule_matching_controller_idx);
4267
                if (ret == 0)
10✔
4268
                        /* We found a matching controller */
4269
                        break;
9✔
4270

4271
                token = strtok_r(NULL, ",", &saveptr);
1✔
4272
        }
4273

4274
        if (!rule->procname) {
9✔
4275
                /*
4276
                 * The rule procname is empty, thus it's a wildcard and
4277
                 * all processes match.
4278
                 */
4279
                found_match = true;
5✔
4280
                goto out;
5✔
4281
        }
4282

4283
        if (!strcmp(rule->procname, procname)) {
4✔
4284
                found_match = true;
2✔
4285
                goto out;
2✔
4286
        }
4287

4288
        if (cgroup_compare_wildcard_procname(rule->procname, procname))
2✔
4289
                found_match = true;
1✔
4290

4291
out:
1✔
4292
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
238✔
4293
                if (controller_list[i])
224✔
4294
                        free(controller_list[i]);
36✔
4295
                if (cgrp_list[i])
224✔
4296
                        free(cgrp_list[i]);
36✔
4297
        }
4298

4299
        return found_match;
14✔
4300
}
4301

4302
static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(uid_t uid, gid_t gid,
420✔
4303
                                                             struct cgroup_rule *rule)
4304
{
4305
        /* Temporary user data */
4306
        struct passwd *usr = NULL;
420✔
4307

4308
        /* Temporary group data */
4309
        struct group *grp = NULL;
420✔
4310

4311
        /* Temporary string pointer */
4312
        char *sp = NULL;
420✔
4313

4314
        /* Loop variable */
4315
        int i = 0;
420✔
4316
        int loglevel;
4317
        bool match_found = false;
420✔
4318

4319
        loglevel = cgroup_get_loglevel();
420✔
4320

4321
        while (rule) {
420✔
4322
                /* Skip "%" which indicates continuation of previous rule. */
4323
                if (rule->username[0] == '%') {
420✔
4324
                        rule = rule->next;
×
4325
                        continue;
×
4326
                }
4327
                /* The wildcard rule always matches. */
4328
                if ((rule->uid == CGRULE_WILD) && (rule->gid == CGRULE_WILD))
420✔
4329
                        return rule;
420✔
4330

4331
                /* This is the simple case of the UID matching. */
4332
                if (rule->uid == uid)
×
4333
                        return rule;
×
4334

4335
                /* This is the simple case of the GID matching. */
4336
                if (rule->gid == gid)
×
4337
                        return rule;
×
4338

4339
                /* If this is a group rule, the UID might be a member. */
4340
                if (rule->username[0] == '@') {
×
4341
                        /* Get the group data. */
4342
                        sp = &(rule->username[1]);
×
4343
                        grp = getgrnam(sp);
×
4344
                        if (!grp) {
×
4345
                                rule = rule->next;
×
4346
                                continue;
×
4347
                        }
4348

4349
                        /* Get the data for UID. */
4350
                        usr = getpwuid(uid);
×
4351
                        if (!usr) {
×
4352
                                rule = rule->next;
×
4353
                                continue;
×
4354
                        }
4355

4356
                        cgroup_dbg("User name: %s UID: %d Group name: %s GID: %d\n",
×
4357
                                   usr->pw_name, uid, grp->gr_name, grp->gr_gid);
4358
                        if (grp->gr_mem[0])
×
4359
                                cgroup_dbg("Group member(s):\n");
×
4360

4361
                        /* If UID is a member of group, we matched. */
4362
                        for (i = 0; grp->gr_mem[i]; i++) {
×
4363
                                if (!(strcmp(usr->pw_name, grp->gr_mem[i])))
×
4364
                                        match_found = true;
×
4365

4366
                                if (match_found && loglevel < CGROUP_LOG_DEBUG)
×
4367
                                        /*
4368
                                         * Only continue to run through the loop if debugging is
4369
                                         * enabled so that we can see all of the group members
4370
                                         */
4371
                                        break;
×
4372

4373
                                cgroup_dbg("\t%s\n", grp->gr_mem[i]);
×
4374
                        }
4375

4376
                        if (match_found)
×
4377
                                return rule;
×
4378
                }
4379

4380
                /* If we haven't matched, try the next rule. */
4381
                rule = rule->next;
×
4382
        }
4383

4384
        /* If we get here, no rules matched. */
4385
        return NULL;
×
4386
}
4387

4388
/**
4389
 * Finds the first rule in the cached list that matches the given UID, GID
4390
 * or PROCESS NAME, and returns a pointer to that rule.
4391
 * This function uses rl_lock.
4392
 *
4393
 * This function may NOT be thread safe.
4394
 *        @param uid The UID to match
4395
 *        @param gid The GID to match
4396
 *        @param procname The PROCESS NAME to match
4397
 *        @return Pointer to the first matching rule, or NULL if no match
4398
 * TODO: Determine thread-safeness and fix if not safe.
4399
 */
4400
static struct cgroup_rule *cgroup_find_matching_rule(uid_t uid, gid_t gid, pid_t pid,
420✔
4401
                                                     const char *procname)
4402
{
4403
        /* Return value */
4404
        struct cgroup_rule *ret = rl.head;
420✔
4405
        char *base = NULL;
420✔
4406

4407
        pthread_rwlock_wrlock(&rl_lock);
420✔
4408
        while (ret) {
838✔
4409
                ret = cgroup_find_matching_rule_uid_gid(uid, gid, ret);
420✔
4410
                if (!ret)
420✔
4411
                        break;
×
4412
                if (cgroup_compare_ignore_rule(ret, pid, procname))
420✔
4413
                        /*
4414
                         * This pid matched a rule that instructs the
4415
                         * cgrules daemon to ignore this process.
4416
                         */
4417
                        break;
×
4418
                if (ret->is_ignore) {
420✔
4419
                        /*
4420
                         * The rule currently being examined is an ignore
4421
                         * rule, but it didn't match this pid. Move on to
4422
                         * the next rule
4423
                         */
4424
                        ret = ret->next;
×
4425
                        continue;
×
4426
                }
4427
                if (!procname)
420✔
4428
                        /* If procname is NULL, return a rule matching UID or GID. */
4429
                        break;
×
4430
                if (!ret->procname)
420✔
4431
                        /* If no process name in a rule, that means wildcard */
4432
                        break;
×
4433
                if (!strcmp(ret->procname, procname))
420✔
4434
                        break;
2✔
4435

4436
                base = cgroup_basename(procname);
418✔
4437
                if (!strcmp(ret->procname, base))
418✔
4438
                        /* Check a rule of basename. */
4439
                        break;
×
4440
                if (cgroup_compare_wildcard_procname(ret->procname, procname))
418✔
4441
                        break;
×
4442
                ret = ret->next;
418✔
4443
                free(base);
418✔
4444
                base = NULL;
418✔
4445
        }
4446
        pthread_rwlock_unlock(&rl_lock);
420✔
4447

4448
        if (base)
420✔
4449
                free(base);
×
4450

4451
        return ret;
420✔
4452
}
4453

4454
/*
4455
 * Procedure the existence of cgroup "prefix" is in subsystem
4456
 * controller_name return 0 on success
4457
 */
4458
int cgroup_exist_in_subsystem(char *controller_name, char *prefix)
×
4459
{
4460
        char path[FILENAME_MAX];
4461
        char *ret_path;
4462
        DIR *dir;
4463
        int ret;
4464

4465
        pthread_rwlock_rdlock(&cg_mount_table_lock);
×
4466
        ret_path = cg_build_path_locked(prefix, path, controller_name);
×
4467
        pthread_rwlock_unlock(&cg_mount_table_lock);
×
4468
        if (!ret_path) {
×
4469
                ret = 1;
×
4470
                goto end;
×
4471
        }
4472

4473
        dir = opendir(path);
×
4474
        if (dir == NULL) {
×
4475
                /* cgroup in wanted subsystem does not exist */
4476
                ret = 1;
×
4477
        } else {
4478
                /* cgroup in wanted subsystem exists */
4479
                ret = 0;
×
4480
                closedir(dir);
×
4481
        }
4482
end:
×
4483
        return ret;
×
4484
}
4485

4486
/*
4487
 * Auxiliary function return a pointer to the string which is copy of
4488
 * input string and end with the slash
4489
 */
4490
char *cgroup_copy_with_slash(char *input)
×
4491
{
4492
        int len = strlen(input);
×
4493
        char *output;
4494

4495
        /* If input does not end with '/', allocate one more space for it */
4496
        if ((input[len-1]) != '/')
×
4497
                len = len+1;
×
4498

4499
        output = (char *)malloc(sizeof(char)*(len+1));
×
4500
        if (output == NULL)
×
4501
                return NULL;
×
4502

4503
        strcpy(output, input);
×
4504
        output[len-1] = '/';
×
4505
        output[len] = '\0';
×
4506

4507
        return output;
×
4508
}
4509

4510
/* Add controller to a group if it is not exists create it */
4511
static int add_controller(struct cgroup **pcgrp, char *cgrp_name,
×
4512
                          char controller_name[FILENAME_MAX])
4513
{
4514
        struct cgroup_controller *controller = NULL;
×
4515
        struct cgroup *cgrp = pcgrp[0];
×
4516
        int ret = 0;
×
4517

4518
        if  (cgrp == NULL) {
×
4519
                /* It is the first controller the cgrp have to be created */
4520
                cgrp = cgroup_new_cgroup(cgrp_name);
×
4521
                if (cgrp == NULL) {
×
4522
                        ret = ECGFAIL;
×
4523
                        goto end;
×
4524
                }
4525
                pcgrp[0] = cgrp;
×
4526
        }
4527

4528
        controller = cgroup_add_controller(cgrp, controller_name);
×
4529
        if (controller == NULL) {
×
4530
                cgroup_free(&cgrp);
×
4531
                ret = ECGFAIL;
×
4532
        }
4533
end:
×
4534
        return ret;
×
4535
}
4536

4537
/*
4538
 * Create control group based given template if the group already don't exist
4539
 * dest is template name with substitute variables tmp is used cgrules rule.
4540
 */
4541
static int cgroup_create_template_group(char *orig_group_name, struct cgroup_rule *tmp, int flags)
×
4542
{
4543

4544
        struct cgroup *template_group = NULL;
×
4545
        char *template_name = NULL;        /* Name of the template.              */
×
4546
        char *group_name = NULL;        /* Name of the group based on         */
×
4547
                                        /* template variables are substituted.*/
4548
        char *template_position;        /* Denotes directory in template      */
4549
                                        /* path which is investigated.        */
4550
        char *group_position;                /* Denotes directory in cgroup path   */
4551
                                        /* which is investigated.             */
4552

4553
        int ret = 0;
×
4554
        int exist;
4555
        int i;
4556

4557
        /* Template name and group name have to have '/' sign at the end */
4558
        template_name = cgroup_copy_with_slash(tmp->destination);
×
4559
        if (template_name == NULL) {
×
4560
                ret = ECGOTHER;
×
4561
                last_errno = errno;
×
4562
                goto end;
×
4563
        }
4564
        group_name = cgroup_copy_with_slash(orig_group_name);
×
4565
        if (group_name == NULL) {
×
4566
                ret = ECGOTHER;
×
4567
                last_errno = errno;
×
4568
                free(template_name);
×
4569
                template_name = NULL;
×
4570
                goto end;
×
4571
        }
4572

4573
        /* Set start positions */
4574
        template_position = strchr(template_name, '/');
×
4575
        group_position = strchr(group_name, '/');
×
4576

4577
        /*
4578
         * Go recursively through whole path to template group and create
4579
         * given directory if it does not exist yet
4580
         */
4581
        while ((group_position != NULL) && (template_position != NULL)) {
×
4582
                /* Set new subpath */
4583
                group_position[0] = '\0';
×
4584
                template_position[0] = '\0';
×
4585
                template_group = NULL;
×
4586

4587
                /* Test for which controllers wanted group does not exist */
4588
                i = 0;
×
4589
                while (tmp->controllers[i] != NULL) {
×
4590
                        exist = cgroup_exist_in_subsystem(tmp->controllers[i], group_name);
×
4591

4592
                        if (exist != 0) {
×
4593
                                /* The cgroup does not exist */
4594
                                ret = add_controller(&template_group, group_name,
×
4595
                                                     tmp->controllers[i]);
4596
                                if  (ret != 0)
×
4597
                                        goto while_end;
×
4598
                        }
4599
                        i++;
×
4600
                }
4601

4602
                if (template_group != NULL) {
×
4603
                        /*  New group have to be created */
4604
                        if (strcmp(group_name, template_name) == 0) {
×
4605
                                /* The prefix cgroup without template */
4606
                                ret = cgroup_create_cgroup(template_group, 0);
×
4607
                        } else {
4608
                                /* Use template to create relevant cgroup */
4609
                                ret = cgroup_config_create_template_group(template_group,
×
4610
                                                                          template_name, flags);
4611
                        }
4612

4613
                        if (ret != 0) {
×
4614
                                cgroup_free(&template_group);
×
4615
                                goto while_end;
×
4616
                        }
4617
                        cgroup_dbg("Group %s created - based on template %s\n", group_name,
×
4618
                                   template_name);
4619

4620
                        cgroup_free(&template_group);
×
4621
                }
4622
                template_position[0] = '/';
×
4623
                group_position[0] = '/';
×
4624
                template_position = strchr(++template_position, '/');
×
4625
                group_position = strchr(++group_position, '/');
×
4626
        }
4627

4628
while_end:
×
4629
        if ((template_position != NULL) && (template_position[0] == '\0'))
×
4630
                template_position[0] = '/';
×
4631
        if ((group_position != NULL) && (group_position[0] == '\0'))
×
4632
                group_position[0] = '/';
×
4633

4634
end:
×
4635
        if (group_name != NULL)
×
4636
                free(group_name);
×
4637
        if (template_name != NULL)
×
4638
                free(template_name);
×
4639

4640
        return ret;
×
4641
}
4642

4643
int cgroup_change_cgroup_flags(uid_t uid, gid_t gid, const char *procname, pid_t pid, int flags)
420✔
4644
{
4645
        /* Temporary pointer to a rule */
4646
        struct cgroup_rule *tmp = NULL;
420✔
4647

4648
        /* Temporary variables for destination substitution */
4649
        char newdest[FILENAME_MAX];
4650
        struct passwd *user_info;
4651
        struct group *group_info;
4652
        int available;
4653
        int written;
4654
        int i, j;
4655

4656
        /* Return codes */
4657
        int ret = 0;
420✔
4658

4659
        /* We need to check this before doing anything else! */
4660
        if (!cgroup_initialized) {
420✔
4661
                cgroup_warn("libcgroup is not initialized\n");
×
4662
                ret = ECGROUPNOTINITIALIZED;
×
4663
                goto finished;
×
4664
        }
4665

4666
        /*
4667
         * User had asked to find the matching rule (if one exist) in the
4668
         * cached rules but the list might be empty due to the inactive
4669
         * cgrulesengd. Lets emulate its behaviour of caching the rules by
4670
         * reloading the rules from the configuration file.
4671
         */
4672
        if ((flags & CGFLAG_USECACHE) && (rl.head == NULL)) {
420✔
4673
                cgroup_warn("no cached rules found, trying to reload from %s.\n",
×
4674
                            CGRULES_CONF_FILE);
4675
                ret = cgroup_reload_cached_rules();
×
4676
                if (ret != 0)
×
4677
                        goto finished;
×
4678
        }
4679

4680
        /*
4681
         * If the user did not ask for cached rules, we must parse the
4682
         * configuration to find a matching rule (if one exists).
4683
         * Else, we'll find the first match in the cached list (rl).
4684
         */
4685
        if (!(flags & CGFLAG_USECACHE)) {
420✔
4686
                cgroup_dbg("Not using cached rules for PID %d.\n", pid);
×
4687
                ret = cgroup_parse_rules(false, uid, gid, procname);
×
4688

4689
                /* The configuration file has an error!  We must exit now. */
4690
                if (ret != -1 && ret != 0) {
×
4691
                        cgroup_err("failed to parse the configuration rules\n");
×
4692
                        goto finished;
×
4693
                }
4694

4695
                /* We did not find a matching rule, so we're done. */
4696
                if (ret == 0) {
×
4697
                        cgroup_dbg("No rule found to match PID: %d, UID: %d, GID: %d\n",
×
4698
                                   pid, uid, gid);
4699
                        goto finished;
×
4700
                }
4701

4702
                /* Otherwise, we did match a rule and it's in trl. */
4703
                tmp = trl.head;
×
4704
        } else {
4705
                /* Find the first matching rule in the cached list. */
4706
                tmp = cgroup_find_matching_rule(uid, gid, pid, procname);
420✔
4707
                if (!tmp) {
420✔
4708
                        cgroup_dbg("No rule found to match PID: %d, UID: %d, GID: %d\n",
418✔
4709
                                   pid, uid, gid);
4710
                        ret = 0;
418✔
4711
                        goto finished;
418✔
4712
                }
4713
        }
4714
        cgroup_dbg("Found matching rule %s for PID: %d, UID: %d, GID: %d\n",
2✔
4715
                   tmp->username, pid, uid, gid);
4716

4717
        if (tmp->is_ignore) {
2✔
4718
                /*
4719
                 * This rule has instructed us that this pid is not to be
4720
                 * processed and should be ignored
4721
                 */
4722
                cgroup_dbg("Matching rule is an ignore rule\n");
×
4723
                ret = 0;
×
4724
                goto finished;
×
4725
        }
4726

4727
        /* If we are here, then we found a matching rule, so execute it. */
4728
        do {
4729
                cgroup_dbg("Executing rule %s for PID %d... ", tmp->username, pid);
2✔
4730

4731
                /* Destination substitutions */
4732
                for (j = i = 0; i < strlen(tmp->destination) &&
22✔
4733
                        (j < FILENAME_MAX - 2); ++i, ++j) {
20✔
4734
                        if (tmp->destination[i] == '%') {
20✔
4735
                                /* How many bytes did we write / error check */
4736
                                written = 0;
×
4737
                                /* How many bytes can we write */
4738
                                available = FILENAME_MAX - j - 2;
×
4739
                                /* Substitution */
4740
                                switch (tmp->destination[++i]) {
×
4741
                                case 'U':
×
4742
                                        written = snprintf(newdest+j, available, "%d", uid);
×
4743
                                        break;
×
4744
                                case 'u':
×
4745
                                        user_info = getpwuid(uid);
×
4746
                                        if (user_info) {
×
4747
                                                written = snprintf(newdest + j, available, "%s",
×
4748
                                                                   user_info->pw_name);
4749
                                        } else {
4750
                                                written = snprintf(newdest + j, available, "%d",
×
4751
                                                                   uid);
4752
                                        }
4753
                                        break;
×
4754
                                case 'G':
×
4755
                                        written = snprintf(newdest + j,        available, "%d", gid);
×
4756
                                        break;
×
4757
                                case 'g':
×
4758
                                        group_info = getgrgid(gid);
×
4759
                                        if (group_info) {
×
4760
                                                written = snprintf(newdest + j,        available, "%s",
×
4761
                                                                   group_info->gr_name);
4762
                                        } else {
4763
                                                written = snprintf(newdest + j,        available, "%d",
×
4764
                                                                   gid);
4765
                                        }
4766
                                        break;
×
4767
                                case 'P':
×
4768
                                        written = snprintf(newdest + j,        available, "%d", pid);
×
4769
                                        break;
×
4770
                                case 'p':
×
4771
                                        if (procname) {
×
4772
                                                written = snprintf(newdest + j,        available, "%s",
×
4773
                                                                   procname);
4774
                                        } else {
4775
                                                written = snprintf(newdest + j,        available, "%d",
×
4776
                                                                   pid);
4777
                                        }
4778
                                        break;
×
4779
                                }
4780
                                written = min(written, available);
×
4781
                                /*
4782
                                 * written<1 only when either error occurred
4783
                                 * during snprintf or if no substitution was made
4784
                                 * at all. In both cases, we want to just copy
4785
                                 * input string.
4786
                                 */
4787
                                if (written < 1) {
×
4788
                                        newdest[j] = '%';
×
4789
                                        if (available > 1)
×
4790
                                                newdest[++j] = tmp->destination[i];
×
4791
                                } else {
4792
                                        /*
4793
                                         * In next iteration, we will write just
4794
                                         * after the substitution, but j will get
4795
                                         * incremented in the meantime.
4796
                                         */
4797
                                        j += written - 1;
×
4798
                                }
4799
                        } else {
4800
                                if (tmp->destination[i] == '\\')
20✔
4801
                                        ++i;
×
4802
                                newdest[j] = tmp->destination[i];
20✔
4803
                        }
4804
                }
4805

4806
                newdest[j] = 0;
2✔
4807
                if (strcmp(newdest, tmp->destination) != 0) {
2✔
4808
                        /* Destination tag contains templates */
4809

4810
                        cgroup_dbg("control group %s is template\n", newdest);
×
4811
                        ret = cgroup_create_template_group(newdest, tmp, flags);
×
4812
                        if (ret) {
×
4813
                                cgroup_warn("failed to create cgroup based on template %s\n",
×
4814
                                            newdest);
4815
                                goto finished;
×
4816
                        }
4817
                }
4818

4819
                /* Apply the rule */
4820
                ret = cgroup_change_cgroup_path(newdest, pid,
2✔
4821
                                                (const char * const *)tmp->controllers);
2✔
4822
                if (ret) {
2✔
4823
                        cgroup_warn("failed to apply the rule. Error was: %d\n", ret);
×
4824
                        goto finished;
×
4825
                }
4826
                cgroup_dbg("OK!\n");
2✔
4827

4828
                /*
4829
                 * Now, check for multi-line rules.  As long as the "next"
4830
                 * rule starts with '%', it's actually part of the rule that
4831
                 * we just executed.
4832
                 */
4833
                tmp = tmp->next;
2✔
4834
        } while (tmp && (tmp->username[0] == '%'));
2✔
4835

4836
finished:
2✔
4837
        return ret;
420✔
4838
}
4839

4840
int cgroup_change_cgroup_uid_gid_flags(uid_t uid, gid_t gid, pid_t pid, int flags)
×
4841
{
4842
        return cgroup_change_cgroup_flags(uid, gid, NULL, pid, flags);
×
4843
}
4844

4845
/**
4846
 * Provides backwards-compatibility with older versions of the API.
4847
 * This function is deprecated, and cgroup_change_cgroup_uid_gid_flags()
4848
 * should be used instead.  In fact, this function simply calls the newer
4849
 * one with flags set to 0 (none).
4850
 *        @param uid The UID to match
4851
 *        @param gid The GID to match
4852
 *        @param pid The PID of the process to move
4853
 *        @return 0 on success, > 0 on error
4854
 */
4855
int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid)
×
4856
{
4857
        return cgroup_change_cgroup_uid_gid_flags(uid, gid, pid, 0);
×
4858
}
4859

4860
/**
4861
 * Changes the cgroup of a program based on the path provided.  In this case,
4862
 * the user must already know into which cgroup the task should be placed and
4863
 * no rules will be parsed.
4864
 *
4865
 *  returns 0 on success.
4866
 */
4867
int cgroup_change_cgroup_path(const char *dest, pid_t pid, const char *const controllers[])
49✔
4868
{
4869
        struct dirent *task_dir = NULL;
49✔
4870
        char path[FILENAME_MAX];
4871
        struct cgroup cgrp;
4872
        int nr, ret;
4873
        pid_t tid;
4874
        DIR *dir;
4875

4876
        if (!cgroup_initialized) {
49✔
4877
                cgroup_warn("libcgroup is not initialized\n");
×
4878
                return ECGROUPNOTINITIALIZED;
×
4879
        }
4880
        memset(&cgrp, 0, sizeof(struct cgroup));
49✔
4881

4882

4883
        if (is_cgroup_mode_unified() && !controllers) {
49✔
4884
                /*
4885
                 * Do not require the user to pass in an array of controller strings on
4886
                 * cgroup v2 systems.  The hierarchy will be the same regardless of
4887
                 * whether controllers are provided or not.
4888
                 */
4889
                strncpy(cgrp.name, dest, FILENAME_MAX);
1✔
4890
                cgrp.name[FILENAME_MAX-1] = '\0';
1✔
4891
        } else {
4892
                if (!controllers)
48✔
4893
                        return ECGINVAL;
×
4894

4895
                ret = cg_prepare_cgroup(&cgrp, pid, dest, controllers);
48✔
4896
                if (ret)
48✔
4897
                        return ret;
×
4898
        }
4899

4900
        /* Add process to cgroup */
4901
        ret = cgroup_attach_task_pid(&cgrp, pid);
49✔
4902
        if (ret) {
49✔
4903
                cgroup_warn("cgroup_attach_task_pid failed: %d\n", ret);
4✔
4904
                goto finished;
4✔
4905
        }
4906

4907
        /* Add all threads to cgroup */
4908
        snprintf(path, FILENAME_MAX, "/proc/%d/task/", pid);
45✔
4909
        dir = opendir(path);
45✔
4910
        if (!dir) {
45✔
4911
                last_errno = errno;
×
4912
                ret = ECGOTHER;
×
4913
                goto finished;
×
4914
        }
4915

4916
        while ((task_dir = readdir(dir)) != NULL) {
186✔
4917
                nr = sscanf(task_dir->d_name, "%i", &tid);
141✔
4918
                if (nr < 1)
141✔
4919
                        continue;
90✔
4920

4921
                if (tid == pid)
51✔
4922
                        continue;
45✔
4923

4924
                ret = cgroup_attach_task_pid(&cgrp, tid);
6✔
4925
                if (ret) {
6✔
4926
                        cgroup_warn("cgroup_attach_task_pid failed: %d\n", ret);
×
4927
                        break;
×
4928
                }
4929
        }
4930

4931
        closedir(dir);
45✔
4932

4933
finished:
49✔
4934
        cgroup_free_controllers(&cgrp);
49✔
4935

4936
        return ret;
49✔
4937
}
4938

4939
/**
4940
 * Changes the cgroup of all running PIDs based on the rules in the config file.
4941
 * If a rules exists for a PID, then the PID is placed in the correct group.
4942
 *
4943
 * This function may be called after creating new control groups to move
4944
 * running PIDs into the newly created control groups.
4945
 *        @return 0 on success, < 0 on error
4946
 */
4947
int cgroup_change_all_cgroups(void)
2✔
4948
{
4949
        struct dirent *pid_dir = NULL;
2✔
4950
        char *path = "/proc/";
2✔
4951
        DIR *dir;
4952

4953
        dir = opendir(path);
2✔
4954
        if (!dir)
2✔
4955
                return -ECGOTHER;
×
4956

4957
        while ((pid_dir = readdir(dir)) != NULL) {
469✔
4958
                int err, pid;
4959
                uid_t euid;
4960
                gid_t egid;
4961
                char *procname = NULL;
467✔
4962

4963
                err = sscanf(pid_dir->d_name, "%i", &pid);
467✔
4964
                if (err < 1)
467✔
4965
                        continue;
129✔
4966

4967
                err = cgroup_get_uid_gid_from_procfs(pid, &euid, &egid);
338✔
4968
                if (err)
338✔
4969
                        continue;
×
4970

4971
                err = cgroup_get_procname_from_procfs(pid, &procname);
338✔
4972
                if (err)
338✔
4973
                        continue;
×
4974

4975
                err = cgroup_change_cgroup_flags(euid, egid, procname, pid, CGFLAG_USECACHE);
338✔
4976
                if (err)
338✔
4977
                        cgroup_dbg("cgroup change pid %i failed\n", pid);
×
4978

4979
                free(procname);
338✔
4980
        }
4981

4982
        closedir(dir);
2✔
4983
        return 0;
2✔
4984
}
4985

4986
/**
4987
 * Print the cached rules table.  This function should be called only after
4988
 * first calling cgroup_parse_config(), but it will work with an empty rule
4989
 * list.
4990
 *        @param fp The file stream to print to
4991
 */
4992
void cgroup_print_rules_config(FILE *fp)
2✔
4993
{
4994
        /* Iterator */
4995
        struct cgroup_rule *itr = NULL;
2✔
4996

4997
        /* Loop variable */
4998
        int i = 0;
2✔
4999

5000
        pthread_rwlock_rdlock(&rl_lock);
2✔
5001

5002
        if (!(rl.head)) {
2✔
5003
                fprintf(fp, "The rules table is empty.\n\n");
×
5004
                pthread_rwlock_unlock(&rl_lock);
×
5005
                return;
×
5006
        }
5007

5008
        itr = rl.head;
2✔
5009
        while (itr) {
4✔
5010
                fprintf(fp, "Rule: %s", itr->username);
2✔
5011
                if (itr->procname)
2✔
5012
                        fprintf(fp, ":%s", itr->procname);
2✔
5013
                fprintf(fp, "\n");
2✔
5014

5015
                if (itr->uid == CGRULE_WILD)
2✔
5016
                        fprintf(fp, "  UID: any\n");
2✔
5017
                else if (itr->uid == CGRULE_INVALID)
×
5018
                        fprintf(fp, "  UID: N/A\n");
×
5019
                else
5020
                        fprintf(fp, "  UID: %d\n", itr->uid);
×
5021

5022
                if (itr->gid == CGRULE_WILD)
2✔
5023
                        fprintf(fp, "  GID: any\n");
2✔
5024
                else if (itr->gid == CGRULE_INVALID)
×
5025
                        fprintf(fp, "  GID: N/A\n");
×
5026
                else
5027
                        fprintf(fp, "  GID: %d\n", itr->gid);
×
5028

5029
                fprintf(fp, "  DEST: %s\n", itr->destination);
2✔
5030

5031
                fprintf(fp, "  CONTROLLERS:\n");
2✔
5032
                for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
34✔
5033
                        if (itr->controllers[i])
32✔
5034
                                fprintf(fp, "    %s\n", itr->controllers[i]);
2✔
5035
                }
5036
                fprintf(fp, "  OPTIONS:\n");
2✔
5037
                if (itr->is_ignore)
2✔
5038
                        fprintf(fp, "    IS_IGNORE: True\n");
×
5039
                else
5040
                        fprintf(fp, "    IS_IGNORE: False\n");
2✔
5041
                fprintf(fp, "\n");
2✔
5042
                itr = itr->next;
2✔
5043
        }
5044
        pthread_rwlock_unlock(&rl_lock);
2✔
5045
}
5046

5047
/**
5048
 * Reloads the rules list, using the given configuration file.
5049
 * This function is probably NOT thread safe (calls cgroup_parse_rules()).
5050
 *        @return 0 on success, > 0 on failure
5051
 */
5052
int cgroup_reload_cached_rules(void)
×
5053
{
5054
        /* Return codes */
5055
        int ret = 0;
×
5056

5057
        cgroup_dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE);
×
5058
        ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL);
×
5059
        if (ret) {
×
5060
                cgroup_warn("error parsing configuration file '%s': %d\n", CGRULES_CONF_FILE, ret);
×
5061
                ret = ECGRULESPARSEFAIL;
×
5062
                goto finished;
×
5063
        }
5064

5065
        #ifdef CGROUP_DEBUG
5066
        cgroup_print_rules_config(stdout);
5067
        #endif
5068
finished:
×
5069
        return ret;
×
5070
}
5071

5072
/**
5073
 * Initializes the rules cache.
5074
 *        @return 0 on success, > 0 on error
5075
 */
5076
int cgroup_init_rules_cache(void)
2✔
5077
{
5078
        /* Return codes */
5079
        int ret = 0;
2✔
5080

5081
        /* Attempt to read the configuration file and cache the rules. */
5082
        ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL);
2✔
5083
        if (ret)
2✔
5084
                cgroup_dbg("Could not initialize rule cache, error was: %d\n", ret);
×
5085

5086
        return ret;
2✔
5087
}
5088

5089
/**
5090
 * cgroup_get_current_controller_path
5091
 * @pid: pid of the current process for which the path is to be determined
5092
 * @controller: name of the controller for which to determine current path
5093
 * @current_path: a pointer that is filled with the value of the current
5094
 *                path as seen in /proc/<pid>/cgroup
5095
 */
5096
int cgroup_get_current_controller_path(pid_t pid, const char *controller, char **current_path)
7✔
5097
{
5098
        enum cg_version_t version;
5099
        enum cg_setup_mode_t mode;
5100
        FILE *pid_cgrp_fd = NULL;
7✔
5101
        bool unified = false;
7✔
5102
        char *path = NULL;
7✔
5103
        int ret;
5104

5105
        if (!cgroup_initialized) {
7✔
5106
                cgroup_warn("libcgroup is not initialized\n");
×
5107
                return ECGROUPNOTINITIALIZED;
×
5108
        }
5109

5110
        if (!current_path)
7✔
5111
                return ECGOTHER;
×
5112

5113
        mode = cgroup_setup_mode();
7✔
5114
        if (mode == CGROUP_MODE_LEGACY && !controller)
7✔
5115
                return ECGOTHER;
×
5116

5117
        /*
5118
         * both unified/hybrid can have the controller mounted as
5119
         * cgroup v2 version.
5120
         */
5121
        if  (!controller) {
7✔
5122
                unified = true;
4✔
5123
        } else {
5124
                ret = cgroup_get_controller_version(controller, &version);
3✔
5125
                if (ret) {
3✔
5126
                        cgroup_warn("Failed to get version of the controller: %s\n", controller);
1✔
5127
                        ret = ECGINVAL;
1✔
5128
                        goto cleanup_path;
1✔
5129
                }
5130
                unified = (version == CGROUP_V2);
2✔
5131
        }
5132

5133
        ret = asprintf(&path, "/proc/%d/cgroup", pid);
6✔
5134
        if (ret <= 0) {
6✔
5135
                cgroup_warn("cannot allocate memory (/proc/pid/cgroup) ret %d\n", ret);
×
5136
                return ret;
×
5137
        }
5138

5139
        ret = ECGROUPNOTEXIST;
6✔
5140
        pid_cgrp_fd = fopen(path, "re");
6✔
5141
        if (!pid_cgrp_fd)
6✔
5142
                goto cleanup_path;
×
5143

5144
        /*
5145
         * Why do we grab the cg_mount_table_lock?, the reason is that the
5146
         * cgroup of a pid can change via the cgroup_attach_task_pid() call.
5147
         * To make sure, we return consistent and safe results, we acquire the
5148
         * lock upfront. We can optimize by acquiring and releasing
5149
         * the lock in the while loop, but that will be more expensive.
5150
         */
5151
        pthread_rwlock_rdlock(&cg_mount_table_lock);
6✔
5152
        while (!feof(pid_cgrp_fd)) {
6✔
5153
                char controllers[FILENAME_MAX];
5154
                char cgrp_path[FILENAME_MAX];
5155
                char *savedptr;
5156
                char *token;
5157
                int num;
5158

5159
                /*
5160
                 * with unified mode, the /proc/pid/cgroup the output is
5161
                 * similar to that of cgroup legacy and hybrid modes:
5162
                 * hierarchy-ID:controller-list:cgroup-path
5163
                 *
5164
                 * the difference is that in cgroup v2:
5165
                 * - hierarchy-ID is always 0 (one hierarchy allowed)
5166
                 * - controller-list is empty
5167
                 */
5168
                if (mode == CGROUP_MODE_UNIFIED || unified) {
6✔
5169
                        ret = fscanf(pid_cgrp_fd, "%d::%4096s\n", &num, cgrp_path);
6✔
5170
                        if (ret != 2) {
6✔
5171
                                /*
5172
                                 * we are interested only in unified format
5173
                                 * line, skip this line.
5174
                                 */
5175
                                if (unified) {
×
5176
                                        ret = fscanf(pid_cgrp_fd, "%*[^\n]\n");
×
5177
                                        if (ret == 0)
×
5178
                                                continue;
×
5179

5180
                                        if (ret == EOF) {
×
5181
                                                last_errno = errno;
×
5182
                                                ret = ECGEOF;
×
5183
                                                goto done;
6✔
5184
                                        }
5185
                                }
5186

5187
                                cgroup_warn("read failed for pid_cgroup_fd ret %d\n", ret);
×
5188
                                last_errno = errno;
×
5189
                                ret = ECGOTHER;
×
5190
                                goto done;
×
5191
                        }
5192

5193
                        /* check if the controller is enabled in cgroup v2 */
5194
                        if (controller) {
6✔
5195
                                ret = cgroupv2_controller_enabled(cgrp_path, controller);
2✔
5196
                                if (ret)
2✔
5197
                                        goto done;
1✔
5198
                        }
5199

5200
                        *current_path = strdup(cgrp_path);
5✔
5201
                        if (!*current_path) {
5✔
5202
                                last_errno = errno;
×
5203
                                ret = ECGOTHER;
×
5204
                                goto done;
×
5205
                        }
5206
                        ret = 0;
5✔
5207
                        goto done;
5✔
5208
                }
5209

5210
                /*
5211
                 * 4096 == FILENAME_MAX, keeping the coverity happy with precision
5212
                 * for the cgrp_path.
5213
                 */
5214
                ret = fscanf(pid_cgrp_fd, "%d:%[^:]:%4096s\n", &num, controllers, cgrp_path);
×
5215
                /*
5216
                 * Magic numbers like "3" seem to be integrating into my daily
5217
                 * life, I need some magic to help make them disappear :)
5218
                 */
5219
                if (ret != 3) {
×
5220
                        cgroup_warn("read failed for pid_cgrp_fd ret %d\n", ret);
×
5221
                        last_errno = errno;
×
5222
                        ret = ECGOTHER;
×
5223
                        goto done;
×
5224
                }
5225

5226
                token = strtok_r(controllers, ",", &savedptr);
×
5227
                while (token) {
×
5228
                        if (strncmp(controller, token, strlen(controller) + 1) == 0) {
×
5229
                                *current_path = strdup(cgrp_path);
×
5230
                                if (!*current_path) {
×
5231
                                        last_errno = errno;
×
5232
                                        ret = ECGOTHER;
×
5233
                                        goto done;
×
5234
                                }
5235
                                ret = 0;
×
5236
                                goto done;
×
5237
                        }
5238
                        token = strtok_r(NULL, ",", &savedptr);
×
5239
                }
5240
        }
5241

5242
done:
×
5243
        pthread_rwlock_unlock(&cg_mount_table_lock);
6✔
5244
        fclose(pid_cgrp_fd);
6✔
5245
cleanup_path:
7✔
5246
        free(path);
7✔
5247

5248
        return ret;
7✔
5249
}
5250

5251
const char *cgroup_strerror(int code)
48✔
5252
{
5253
        int idx = code % ECGROUPNOTCOMPILED;
48✔
5254

5255
        if (code == ECGOTHER) {
48✔
5256
#ifdef STRERROR_R_CHAR_P
5257
                return strerror_r(cgroup_get_last_errno(), errtext, MAXLEN);
20✔
5258
#else
5259
                return strerror_r(cgroup_get_last_errno(), errtext, sizeof(errtext)) ?
5260
                        "unknown error" : errtext;
5261
#endif
5262
        }
5263
        if (idx >= ARRAY_SIZE(cgroup_strerror_codes))
28✔
5264
                return "Invalid error code";
×
5265

5266
        return cgroup_strerror_codes[idx];
28✔
5267
}
5268

5269
/**
5270
 * Return last errno, which caused ECGOTHER error.
5271
 */
5272
int cgroup_get_last_errno(void)
20✔
5273
{
5274
        return last_errno;
20✔
5275
}
5276

5277
static int cg_walk_node(FTS *fts, FTSENT *ent, const int depth, struct cgroup_file_info *info,
8,512✔
5278
                        int dir)
5279
{
5280
        int ret = 0;
8,512✔
5281

5282
        if (!cgroup_initialized)
8,512✔
5283
                return ECGROUPNOTINITIALIZED;
×
5284

5285
        cgroup_dbg("seeing file %s\n", ent->fts_path);
8,512✔
5286

5287
        info->path = ent->fts_name;
8,512✔
5288
        info->parent = ent->fts_parent->fts_name;
8,512✔
5289
        info->full_path = ent->fts_path;
8,512✔
5290
        info->depth = ent->fts_level;
8,512✔
5291
        info->type = CGROUP_FILE_TYPE_OTHER;
8,512✔
5292

5293
        if (depth && (info->depth > depth))
8,512✔
5294
                return 0;
×
5295

5296
        switch (ent->fts_info) {
8,512✔
5297
        case FTS_DNR:
×
5298
        case FTS_ERR:
5299
                errno = ent->fts_errno;
×
5300
                break;
×
5301
        case FTS_D:
165✔
5302
                if (dir & CGROUP_WALK_TYPE_PRE_DIR)
165✔
5303
                        info->type = CGROUP_FILE_TYPE_DIR;
121✔
5304
                break;
165✔
5305
        case FTS_DC:
165✔
5306
        case FTS_NSOK:
5307
        case FTS_NS:
5308
        case FTS_DP:
5309
                if (dir & CGROUP_WALK_TYPE_POST_DIR)
165✔
5310
                        info->type = CGROUP_FILE_TYPE_DIR;
71✔
5311
                break;
165✔
5312
        case FTS_F:
8,182✔
5313
                info->type = CGROUP_FILE_TYPE_FILE;
8,182✔
5314
                break;
8,182✔
5315
        case FTS_DEFAULT:
×
5316
                break;
×
5317
        }
5318
        return ret;
8,512✔
5319
}
5320

5321
int cgroup_walk_tree_next(int depth, void **handle, struct cgroup_file_info *info, int base_level)
8,512✔
5322
{
5323
        struct cgroup_tree_handle *entry;
5324
        FTSENT *ent;
5325
        int ret = 0;
8,512✔
5326

5327
        if (!cgroup_initialized)
8,512✔
5328
                return ECGROUPNOTINITIALIZED;
×
5329

5330
        if (!handle)
8,512✔
5331
                return ECGINVAL;
×
5332

5333
        entry = (struct cgroup_tree_handle *) *handle;
8,512✔
5334

5335
        ent = fts_read(entry->fts);
8,512✔
5336
        if (!ent)
8,512✔
5337
                return ECGEOF;
41✔
5338
        if (!base_level && depth)
8,471✔
5339
                base_level = ent->fts_level + depth;
×
5340

5341
        ret = cg_walk_node(entry->fts, ent, base_level, info, entry->flags);
8,471✔
5342

5343
        *handle = entry;
8,471✔
5344
        return ret;
8,471✔
5345
}
5346

5347
int cgroup_walk_tree_end(void **handle)
41✔
5348
{
5349
        struct cgroup_tree_handle *entry;
5350

5351
        if (!cgroup_initialized)
41✔
5352
                return ECGROUPNOTINITIALIZED;
×
5353

5354
        if (!handle)
41✔
5355
                return ECGINVAL;
×
5356

5357
        entry = (struct cgroup_tree_handle *) *handle;
41✔
5358

5359
        fts_close(entry->fts);
41✔
5360
        free(entry);
41✔
5361
        *handle = NULL;
41✔
5362

5363
        return 0;
41✔
5364
}
5365

5366
/* TODO: Need to decide a better place to put this function. */
5367
int cgroup_walk_tree_begin(const char *controller, const char *base_path, int depth, void **handle,
41✔
5368
                           struct cgroup_file_info *info, int *base_level)
5369
{
5370
        struct cgroup_tree_handle *entry;
5371
        char full_path[FILENAME_MAX];
5372
        char *cg_path[2];
5373
        FTSENT *ent;
5374
        int ret = 0;
41✔
5375

5376
        if (!cgroup_initialized)
41✔
5377
                return ECGROUPNOTINITIALIZED;
×
5378

5379
        if (!handle)
41✔
5380
                return ECGINVAL;
×
5381

5382
        cgroup_dbg("path is %s\n", base_path);
41✔
5383

5384
        if (!cg_build_path(base_path, full_path, controller))
41✔
5385
                return ECGOTHER;
×
5386

5387
        entry = calloc(1, sizeof(struct cgroup_tree_handle));
41✔
5388

5389
        if (!entry) {
41✔
5390
                last_errno = errno;
×
5391
                *handle = NULL;
×
5392
                return ECGOTHER;
×
5393
        }
5394

5395
        entry->flags |= CGROUP_WALK_TYPE_PRE_DIR;
41✔
5396

5397
        *base_level = 0;
41✔
5398
        cg_path[0] = full_path;
41✔
5399
        cg_path[1] = NULL;
41✔
5400

5401
        entry->fts = fts_open(cg_path, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
41✔
5402
        if (entry->fts == NULL) {
41✔
5403
                free(entry);
×
5404
                last_errno = errno;
×
5405
                *handle = NULL;
×
5406
                return ECGOTHER;
×
5407
        }
5408
        ent = fts_read(entry->fts);
41✔
5409
        if (!ent) {
41✔
5410
                cgroup_warn("fts_read failed\n");
×
5411
                fts_close(entry->fts);
×
5412
                free(entry);
×
5413
                *handle = NULL;
×
5414
                return ECGINVAL;
×
5415
        }
5416
        if (!*base_level && depth)
41✔
5417
                *base_level = ent->fts_level + depth;
×
5418

5419
        ret = cg_walk_node(entry->fts, ent, *base_level, info, entry->flags);
41✔
5420
        if (ret != 0) {
41✔
5421
                fts_close(entry->fts);
×
5422
                free(entry);
×
5423
                *handle = NULL;
×
5424
        } else {
5425
                *handle = entry;
41✔
5426
        }
5427
        return ret;
41✔
5428
}
5429

5430
int cgroup_walk_tree_set_flags(void **handle, int flags)
27✔
5431
{
5432
        struct cgroup_tree_handle *entry;
5433

5434
        if (!cgroup_initialized)
27✔
5435
                return ECGROUPNOTINITIALIZED;
×
5436

5437
        if (!handle)
27✔
5438
                return ECGINVAL;
×
5439

5440
        if ((flags & CGROUP_WALK_TYPE_PRE_DIR) &&
27✔
5441
            (flags & CGROUP_WALK_TYPE_POST_DIR))
×
5442
                return ECGINVAL;
×
5443

5444
        entry = (struct cgroup_tree_handle *) *handle;
27✔
5445
        entry->flags = flags;
27✔
5446

5447
        *handle = entry;
27✔
5448
        return 0;
27✔
5449
}
5450

5451
/*
5452
 * This parses a stat line which is in the form of (name value) pair
5453
 * separated by a space.
5454
 */
5455
static int cg_read_stat(FILE *fp, struct cgroup_stat *cgrp_stat)
×
5456
{
5457
        char *saveptr = NULL;
×
5458
        ssize_t read_bytes;
5459
        char *line = NULL;
×
5460
        size_t len = 0;
×
5461
        char *token;
5462
        int ret = 0;
×
5463

5464
        read_bytes = getline(&line, &len, fp);
×
5465
        if (read_bytes == -1) {
×
5466
                ret = ECGEOF;
×
5467
                goto out_free;
×
5468
        }
5469

5470
        token = strtok_r(line, " ", &saveptr);
×
5471
        if (!token) {
×
5472
                ret = ECGINVAL;
×
5473
                goto out_free;
×
5474
        }
5475
        strncpy(cgrp_stat->name, token, FILENAME_MAX - 1);
×
5476

5477
        token = strtok_r(NULL, " ", &saveptr);
×
5478
        if (!token) {
×
5479
                ret = ECGINVAL;
×
5480
                goto out_free;
×
5481
        }
5482
        strncpy(cgrp_stat->value, token, CG_VALUE_MAX - 1);
×
5483

5484
out_free:
×
5485
        free(line);
×
5486

5487
        return ret;
×
5488
}
5489

5490
int cgroup_read_value_end(void **handle)
219✔
5491
{
5492
        FILE *fp;
5493

5494
        if (!cgroup_initialized)
219✔
5495
                return ECGROUPNOTINITIALIZED;
×
5496

5497
        if (!handle)
219✔
5498
                return ECGINVAL;
×
5499

5500
        fp = (FILE *)*handle;
219✔
5501
        fclose(fp);
219✔
5502
        *handle = NULL;
219✔
5503

5504
        return 0;
219✔
5505
}
5506

5507
int cgroup_read_value_next(void **handle, char *buffer, int max)
291✔
5508
{
5509
        char *ret_c;
5510
        int ret = 0;
291✔
5511
        FILE *fp;
5512

5513
        if (!cgroup_initialized)
291✔
5514
                return ECGROUPNOTINITIALIZED;
×
5515

5516
        if (!buffer || !handle)
291✔
5517
                return ECGINVAL;
×
5518

5519
        fp = (FILE *)*handle;
291✔
5520
        ret_c = fgets(buffer, max, fp);
291✔
5521
        if (ret_c == NULL)
291✔
5522
                ret = ECGEOF;
181✔
5523

5524
        return ret;
291✔
5525
}
5526

5527
int cgroup_read_value_begin(const char * const controller, const char *path,
222✔
5528
                            const char * const name, void **handle, char *buffer, int max)
5529
{
5530
        char stat_file[FILENAME_MAX + sizeof(name)];
5531
        char stat_path[FILENAME_MAX];
5532
        char *ret_c = NULL;
222✔
5533
        int ret = 0;
222✔
5534
        FILE *fp;
5535

5536
        if (!cgroup_initialized)
222✔
5537
                return ECGROUPNOTINITIALIZED;
×
5538

5539
        if (!buffer || !handle)
222✔
5540
                return ECGINVAL;
×
5541

5542
        if (!cg_build_path(path, stat_path, controller))
222✔
5543
                return ECGOTHER;
×
5544

5545
        snprintf(stat_file, sizeof(stat_file), "%s/%s", stat_path, name);
222✔
5546
        fp = fopen(stat_file, "re");
222✔
5547
        if (!fp) {
222✔
5548
                cgroup_warn("fopen failed\n");
3✔
5549
                last_errno = errno;
3✔
5550
                *handle = NULL;
3✔
5551
                return ECGOTHER;
3✔
5552
        }
5553

5554
        ret_c = fgets(buffer, max, fp);
219✔
5555
        if (ret_c == NULL)
219✔
5556
                ret = ECGEOF;
8✔
5557

5558
        *handle = fp;
219✔
5559

5560
        return ret;
219✔
5561
}
5562

5563
int cgroup_read_stats_end(void **handle)
×
5564
{
5565
        FILE *fp;
5566

5567
        if (!cgroup_initialized)
×
5568
                return ECGROUPNOTINITIALIZED;
×
5569

5570
        if (!handle)
×
5571
                return ECGINVAL;
×
5572

5573
        fp = (FILE *)*handle;
×
5574
        if (fp == NULL)
×
5575
                return ECGINVAL;
×
5576

5577
        fclose(fp);
×
5578

5579
        return 0;
×
5580
}
5581

5582
int cgroup_read_stats_next(void **handle, struct cgroup_stat *cgrp_stat)
×
5583
{
5584
        FILE *fp;
5585
        int ret = 0;
×
5586

5587
        if (!cgroup_initialized)
×
5588
                return ECGROUPNOTINITIALIZED;
×
5589

5590
        if (!handle || !cgrp_stat)
×
5591
                return ECGINVAL;
×
5592

5593
        fp = (FILE *)*handle;
×
5594
        ret = cg_read_stat(fp, cgrp_stat);
×
5595
        *handle = fp;
×
5596

5597
        return ret;
×
5598
}
5599

5600
/* TODO: Need to decide a better place to put this function. */
5601
int cgroup_read_stats_begin(const char *controller, const char *path, void **handle,
×
5602
                            struct cgroup_stat *cgrp_stat)
5603
{
5604
        char stat_file[FILENAME_MAX + sizeof(".stat")];
5605
        char stat_path[FILENAME_MAX];
5606
        int ret = 0;
×
5607
        FILE *fp;
5608

5609
        if (!cgroup_initialized)
×
5610
                return ECGROUPNOTINITIALIZED;
×
5611

5612
        if (!cgrp_stat || !handle)
×
5613
                return ECGINVAL;
×
5614

5615
        if (!cg_build_path(path, stat_path, controller))
×
5616
                return ECGOTHER;
×
5617

5618
        snprintf(stat_file, sizeof(stat_file), "%s/%s.stat", stat_path, controller);
×
5619

5620
        fp = fopen(stat_file, "re");
×
5621
        if (!fp) {
×
5622
                cgroup_warn("fopen failed\n");
×
5623
                return ECGINVAL;
×
5624
        }
5625

5626
        ret = cg_read_stat(fp, cgrp_stat);
×
5627
        *handle = fp;
×
5628

5629
        return ret;
×
5630
}
5631

5632
int cgroup_get_task_end(void **handle)
×
5633
{
5634
        if (!cgroup_initialized)
×
5635
                return ECGROUPNOTINITIALIZED;
×
5636

5637
        if (!*handle)
×
5638
                return ECGINVAL;
×
5639

5640
        fclose((FILE *) *handle);
×
5641
        *handle = NULL;
×
5642

5643
        return 0;
×
5644
}
5645

5646
int cgroup_get_task_next(void **handle, pid_t *pid)
×
5647
{
5648
        int ret;
5649

5650
        if (!cgroup_initialized)
×
5651
                return ECGROUPNOTINITIALIZED;
×
5652

5653
        if (!handle)
×
5654
                return ECGINVAL;
×
5655

5656
        ret = fscanf((FILE *) *handle, "%u", pid);
×
5657

5658
        if (ret != 1) {
×
5659
                if (ret == EOF)
×
5660
                        return ECGEOF;
×
5661
                last_errno = errno;
×
5662
                return ECGOTHER;
×
5663
        }
5664

5665
        return 0;
×
5666
}
5667

5668
int cgroup_get_task_begin(const char *cgrp, const char *controller, void **handle, pid_t *pid)
×
5669
{
5670
        char path[FILENAME_MAX];
5671
        char *fullpath = NULL;
×
5672
        int ret = 0;
×
5673

5674
        if (!cgroup_initialized)
×
5675
                return ECGROUPNOTINITIALIZED;
×
5676

5677
        if (!cg_build_path(cgrp, path, controller))
×
5678
                return ECGOTHER;
×
5679

5680
        ret = asprintf(&fullpath, "%s/tasks", path);
×
5681

5682
        if (ret < 0) {
×
5683
                last_errno = errno;
×
5684
                return ECGOTHER;
×
5685
        }
5686

5687
        *handle = (void *) fopen(fullpath, "re");
×
5688
        free(fullpath);
×
5689

5690
        if (!*handle) {
×
5691
                last_errno = errno;
×
5692
                return ECGOTHER;
×
5693
        }
5694
        ret = cgroup_get_task_next(handle, pid);
×
5695

5696
        return ret;
×
5697
}
5698

5699
int cgroup_get_controller_end(void **handle)
8✔
5700
{
5701
        int *pos = (int *) *handle;
8✔
5702

5703
        if (!cgroup_initialized)
8✔
5704
                return ECGROUPNOTINITIALIZED;
×
5705

5706
        if (!pos)
8✔
5707
                return ECGINVAL;
×
5708

5709
        free(pos);
8✔
5710
        *handle = NULL;
8✔
5711

5712
        return 0;
8✔
5713
}
5714

5715
int cgroup_get_controller_next(void **handle, struct cgroup_mount_point *info)
72✔
5716
{
5717
        int *pos = (int *) *handle;
72✔
5718
        int ret = 0;
72✔
5719

5720
        if (!cgroup_initialized)
72✔
5721
                return ECGROUPNOTINITIALIZED;
×
5722

5723
        if (!pos)
72✔
5724
                return ECGINVAL;
×
5725

5726
        if (!info)
72✔
5727
                return ECGINVAL;
×
5728

5729
        pthread_rwlock_rdlock(&cg_mount_table_lock);
72✔
5730

5731
        if (cg_mount_table[*pos].name[0] == '\0') {
72✔
5732
                ret = ECGEOF;
×
5733
                goto out_unlock;
×
5734
        }
5735

5736
        if (strncmp(cg_mount_table[*pos].name, CGRP_FILE_PREFIX, CONTROL_NAMELEN_MAX) == 0)
72✔
5737
                /*
5738
                 * For now, hide the "cgroup" pseudo-controller from the user.  This may be
5739
                 * worth revisiting in the future.
5740
                 */
5741
                (*pos)++;
8✔
5742

5743
        if (cg_mount_table[*pos].name[0] == '\0') {
72✔
5744
                ret = ECGEOF;
8✔
5745
                goto out_unlock;
8✔
5746
        }
5747

5748
        strncpy(info->name, cg_mount_table[*pos].name, FILENAME_MAX - 1);
64✔
5749
        info->name[FILENAME_MAX - 1] = '\0';
64✔
5750

5751
        strncpy(info->path, cg_mount_table[*pos].mount.path, FILENAME_MAX - 1);
64✔
5752
        info->path[FILENAME_MAX - 1] = '\0';
64✔
5753

5754
        (*pos)++;
64✔
5755
        *handle = pos;
64✔
5756

5757
out_unlock:
72✔
5758
        pthread_rwlock_unlock(&cg_mount_table_lock);
72✔
5759

5760
        return ret;
72✔
5761
}
5762

5763
int cgroup_get_controller_begin(void **handle, struct cgroup_mount_point *info)
8✔
5764
{
5765
        int *pos;
5766

5767
        if (!cgroup_initialized)
8✔
5768
                return ECGROUPNOTINITIALIZED;
×
5769

5770
        if (!info)
8✔
5771
                return ECGINVAL;
×
5772

5773
        pos = malloc(sizeof(int));
8✔
5774

5775
        if (!pos) {
8✔
5776
                last_errno = errno;
×
5777
                return ECGOTHER;
×
5778
        }
5779

5780
        *pos = 0;
8✔
5781

5782
        *handle = pos;
8✔
5783

5784
        return cgroup_get_controller_next(handle, info);
8✔
5785
}
5786

5787
/**
5788
 * Get process data (euid and egid) from /proc/<pid>/status file.
5789
 * @param pid: The process id
5790
 * @param euid: The uid of param pid
5791
 * @param egid: The gid of param pid
5792
 * @return 0 on success, > 0 on error.
5793
 */
5794
int cgroup_get_uid_gid_from_procfs(pid_t pid, uid_t *euid, gid_t *egid)
420✔
5795
{
5796
        char path[FILENAME_MAX];
5797
        uid_t ruid, suid, fsuid;
5798
        gid_t rgid, sgid, fsgid;
5799
        bool found_euid = false;
420✔
5800
        bool found_egid = false;
420✔
5801
        char buf[4092];
5802
        FILE *f;
5803

5804
        sprintf(path, "/proc/%d/status", pid);
420✔
5805
        f = fopen(path, "re");
420✔
5806
        if (!f)
420✔
5807
                return ECGROUPNOTEXIST;
×
5808

5809
        while (fgets(buf, sizeof(buf), f)) {
4,200✔
5810
                if (!strncmp(buf, "Uid:", 4)) {
4,200✔
5811
                        if (sscanf((buf + strlen("Uid:") + 1), "%d%d%d%d",
420✔
5812
                                    &ruid, euid, &suid, &fsuid) != 4)
5813
                                break;
×
5814
                        cgroup_dbg("Scanned proc values are %d %d %d %d\n",
420✔
5815
                                   ruid, *euid, suid, fsuid);
5816
                        found_euid = true;
420✔
5817
                } else if (!strncmp(buf, "Gid:", 4)) {
3,780✔
5818
                        if (sscanf((buf + strlen("Gid:") + 1), "%d%d%d%d",
420✔
5819
                                   &rgid, egid, &sgid, &fsgid) != 4)
5820
                                break;
×
5821
                        cgroup_dbg("Scanned proc values are %d %d %d %d\n",
420✔
5822
                                   rgid, *egid, sgid, fsgid);
5823
                        found_egid = true;
420✔
5824
                }
5825
                if (found_euid && found_egid)
4,200✔
5826
                        break;
420✔
5827
        }
5828
        fclose(f);
420✔
5829

5830
        if (!found_euid || !found_egid) {
420✔
5831
                /*
5832
                 * This method doesn't match the file format of
5833
                 * /proc/<pid>/status. The format has been changed and we
5834
                 * should catch up the change.
5835
                 */
5836
                cgroup_warn("invalid file format of /proc/%d/status\n", pid);
×
5837
                return ECGFAIL;
×
5838
        }
5839
        return 0;
420✔
5840
}
5841

5842
/**
5843
 * Given a pid, this function will return the controllers and cgroups that
5844
 * the pid is a member of. The caller is expected to allocate the
5845
 * controller_list[] and cgroup_list[] arrays as well as null each entry in
5846
 * the arrays.  This function will allocate the necessary memory for each
5847
 * string within the arrays.
5848
 *
5849
 *        @param pid The process id
5850
 *        @param cgrp_list[] An array of char pointers to hold the cgroups
5851
 *        @param controller_list[] An array of char pointers to hold the list
5852
 *               of controllers
5853
 *        @param list_len The size of the arrays
5854
 */
5855
STATIC int cg_get_cgroups_from_proc_cgroups(pid_t pid, char *cgrp_list[],
18✔
5856
                                            char *controller_list[], int list_len)
5857
{
5858
        char path[FILENAME_MAX];
5859
        char *stok_buff = NULL;
18✔
5860
        size_t buff_len;
5861
        char buf[4092];
5862
        int ret = 0;
18✔
5863
        int idx = 0;
18✔
5864
        FILE *f;
5865

5866
#ifdef UNIT_TEST
5867
        sprintf(path, "%s", TEST_PROC_PID_CGROUP_FILE);
18✔
5868
#else
5869
        sprintf(path, "/proc/%d/cgroup", pid);
×
5870
#endif
5871
        f = fopen(path, "re");
18✔
5872
        if (!f)
18✔
5873
                return ECGROUPNOTEXIST;
×
5874

5875
        while (fgets(buf, sizeof(buf), f)) {
71✔
5876
                /*
5877
                 * Each line in /proc/{pid}/cgroup is like the following:
5878
                 *
5879
                 * {cg#}:{controller}:{cgname}
5880
                 *
5881
                 * e.g.
5882
                 * 7:devices:/user.slice
5883
                 */
5884

5885
                /* Read in the cgroup number.  We don't care about it */
5886
                stok_buff = strtok(buf, ":");
54✔
5887
                /* Read in the controller name */
5888
                stok_buff = strtok(NULL, ":");
54✔
5889

5890
                /*
5891
                 * After this point, we have allocated memory.  If we return
5892
                 * an error code after this, it's up to us to free the memory
5893
                 * we allocated
5894
                 */
5895
                controller_list[idx] = strndup(stok_buff, strlen(stok_buff) + 1);
54✔
5896

5897
                /* Read in the cgroup name */
5898
                stok_buff = strtok(NULL, ":");
54✔
5899

5900
                if (stok_buff == NULL) {
54✔
5901
                        /*
5902
                         * An empty controller is reported on some kernels.
5903
                         * It may look like this:
5904
                         * 0::/user.slice/user-1000.slice/session-1.scope
5905
                         *
5906
                         * Ignore this controller and move on.  Note that we
5907
                         * need to free the controller list entry we made.
5908
                         */
5909
                        free(controller_list[idx]);
4✔
5910
                        controller_list[idx] = NULL;
4✔
5911
                        continue;
4✔
5912
                }
5913

5914
                buff_len = strlen(stok_buff);
50✔
5915
                if (stok_buff[buff_len - 1] == '\n')
50✔
5916
                        buff_len--; /* Don't copy the trailing newline char */
38✔
5917

5918
                /* Read in the cgroup name */
5919
                if (buff_len > 1) {
50✔
5920
                        /* Strip off the leading '/' for every cgroup but the root cgroup */
5921
                        cgrp_list[idx] = malloc(buff_len);
34✔
5922
                        snprintf(cgrp_list[idx], buff_len, "%s", &stok_buff[1]);
34✔
5923
                } else {
5924
                        /* Retain the leading '/' since we're in the root cgroup */
5925
                        cgrp_list[idx] = strndup(stok_buff, buff_len);
16✔
5926
                }
5927

5928
                idx++;
50✔
5929
                if (idx >= list_len) {
50✔
5930
                        cgroup_warn("Maximum mount elements reached. Consider increasing ");
1✔
5931
                        cgroup_cont("MAX_MNT_ELEMENTS\n");
1✔
5932
                        break;
1✔
5933
                }
5934
        }
5935
        fclose(f);
18✔
5936
        return ret;
18✔
5937
}
5938

5939
/**
5940
 * Get process name from /proc/<pid>/status file.
5941
 * @param pid: The process id
5942
 * @param pname_status : The process name
5943
 * @return 0 on success, > 0 on error.
5944
 */
5945
static int cg_get_procname_from_proc_status(pid_t pid, char **procname_status)
420✔
5946
{
5947
        char path[FILENAME_MAX];
5948
        int ret = ECGFAIL;
420✔
5949
        char buf[4092];
5950
        FILE *f;
5951
        int len;
5952

5953
        sprintf(path, "/proc/%d/status", pid);
420✔
5954
        f = fopen(path, "re");
420✔
5955
        if (!f)
420✔
5956
                return ECGROUPNOTEXIST;
×
5957

5958
        while (fgets(buf, sizeof(buf), f)) {
420✔
5959
                if (!strncmp(buf, "Name:", 5)) {
420✔
5960
                        len = strlen(buf);
420✔
5961
                        if (buf[len - 1] == '\n')
420✔
5962
                                buf[len - 1] = '\0';
420✔
5963
                        *procname_status = strdup(buf + strlen("Name:") + 1);
420✔
5964
                        if (*procname_status == NULL) {
420✔
5965
                                last_errno = errno;
×
5966
                                ret = ECGOTHER;
×
5967
                                break;
×
5968
                        }
5969
                        ret = 0;
420✔
5970
                        break;
420✔
5971
                }
5972
        }
5973
        fclose(f);
420✔
5974
        return ret;
420✔
5975
}
5976

5977
/**
5978
 * Get process name from /proc/<pid>/cmdline file.
5979
 * This function is mainly for getting a script name (shell, perl, etc).
5980
 * A script name is written into the second or later argument of
5981
 * /proc/<pid>/cmdline. This function gets each argument and
5982
 * compares it to a process name taken from /proc/<pid>/status.
5983
 * @param pid: The process id
5984
 * @param pname_status : The process name taken from /proc/<pid>/status
5985
 * @param pname_cmdline: The process name taken from /proc/<pid>/cmdline
5986
 * @return 0 on success, > 0 on error.
5987
 */
5988
static int cg_get_procname_from_proc_cmdline(pid_t pid, const char *pname_status,
30✔
5989
                                             char **pname_cmdline)
5990
{
5991
        char pid_cwd_path[FILENAME_MAX];
5992
        char pid_cmd_path[FILENAME_MAX];
5993
        char buf_pname[FILENAME_MAX];
5994
        char buf_cwd[FILENAME_MAX];
5995
        int ret = ECGFAIL;
30✔
5996
        int len = 0;
30✔
5997
        int c = 0;
30✔
5998
        FILE *f;
5999

6000
        memset(buf_cwd, '\0', sizeof(buf_cwd));
30✔
6001
        sprintf(pid_cwd_path, "/proc/%d/cwd", pid);
30✔
6002

6003
        if (readlink(pid_cwd_path, buf_cwd, sizeof(buf_cwd)) < 0)
30✔
6004
                return ECGROUPNOTEXIST;
×
6005

6006
        /* readlink doesn't append a null */
6007
        buf_cwd[FILENAME_MAX - 1] = '\0';
30✔
6008

6009
        sprintf(pid_cmd_path, "/proc/%d/cmdline", pid);
30✔
6010
        f = fopen(pid_cmd_path, "re");
30✔
6011
        if (!f)
30✔
6012
                return ECGROUPNOTEXIST;
×
6013

6014
        while (c != EOF) {
461✔
6015
                c = fgetc(f);
460✔
6016
                if ((c != EOF) && (c != '\0') && (len < FILENAME_MAX - 1)) {
460✔
6017
                        buf_pname[len] = c;
425✔
6018
                        len++;
425✔
6019
                        continue;
425✔
6020
                }
6021
                buf_pname[len] = '\0';
35✔
6022

6023
                if (len == FILENAME_MAX - 1)
35✔
6024
                        while ((c != EOF) && (c != '\0'))
×
6025
                                c = fgetc(f);
×
6026

6027
                /*
6028
                 * The taken process name from /proc/<pid>/status is
6029
                 * shortened to 15 characters if it is over. So the name
6030
                 * should be compared by its length.
6031
                 */
6032
                if (strncmp(pname_status, basename(buf_pname), TASK_COMM_LEN - 1)) {
35✔
6033
                        len = 0;
6✔
6034
                        continue;
6✔
6035
                }
6036

6037
                if (buf_pname[0] == '/') {
29✔
6038
                        *pname_cmdline = strdup(buf_pname);
17✔
6039
                        if (*pname_cmdline == NULL) {
17✔
6040
                                last_errno = errno;
×
6041
                                ret = ECGOTHER;
×
6042
                                break;
×
6043
                        }
6044
                        ret = 0;
17✔
6045
                        break;
17✔
6046
                }
6047

6048
                strcat(buf_cwd, "/");
12✔
6049
                strcat(buf_cwd, buf_pname);
12✔
6050
                if (!realpath(buf_cwd, pid_cmd_path)) {
12✔
6051
                        last_errno = errno;
11✔
6052
                        ret = ECGOTHER;
11✔
6053
                        break;
11✔
6054
                }
6055

6056
                *pname_cmdline = strdup(pid_cmd_path);
1✔
6057
                if (*pname_cmdline == NULL) {
1✔
6058
                        last_errno = errno;
×
6059
                        ret = ECGOTHER;
×
6060
                        break;
×
6061
                }
6062
                ret = 0;
1✔
6063
                break;
1✔
6064
        }
6065
        fclose(f);
30✔
6066
        return ret;
30✔
6067
}
6068

6069
/**
6070
 * Get a process name from /proc file system.
6071
 * This function allocates memory for a process name, writes a process
6072
 * name onto it. So a caller should free the memory when unusing it.
6073
 * @param pid: The process id
6074
 * @param procname: The process name
6075
 * @return 0 on success, > 0 on error.
6076
 */
6077
int cgroup_get_procname_from_procfs(pid_t pid, char **procname)
420✔
6078
{
6079
        char path[FILENAME_MAX];
6080
        char buf[FILENAME_MAX];
6081
        char *pname_cmdline;
6082
        char *pname_status;
6083
        int ret;
6084

6085
        ret = cg_get_procname_from_proc_status(pid, &pname_status);
420✔
6086
        if (ret)
420✔
6087
                return ret;
×
6088

6089
        /* Get the full patch of process name from /proc/<pid>/exe. */
6090
        memset(buf, '\0', sizeof(buf));
420✔
6091
        snprintf(path, FILENAME_MAX, "/proc/%d/exe", pid);
420✔
6092
        if (readlink(path, buf, sizeof(buf)) < 0) {
420✔
6093
                /*
6094
                 * readlink() fails if a kernel thread, and a process name
6095
                 * is taken from /proc/<pid>/status.
6096
                 */
6097
                *procname = pname_status;
242✔
6098
                return 0;
242✔
6099
        }
6100
        /* readlink doesn't append a null */
6101
        buf[FILENAME_MAX - 1] = '\0';
178✔
6102

6103
        if (!strncmp(pname_status, basename(buf), TASK_COMM_LEN - 1)) {
178✔
6104
                /*
6105
                 * The taken process name from /proc/<pid>/status is
6106
                 * shortened to 15 characters if it is over. So the name
6107
                 * should be compared by its length.
6108
                 */
6109
                free(pname_status);
148✔
6110
                *procname = strdup(buf);
148✔
6111
                if (*procname == NULL) {
148✔
6112
                        last_errno = errno;
×
6113
                        return ECGOTHER;
×
6114
                }
6115
                return 0;
148✔
6116
        }
6117

6118
        /*
6119
         * The above strncmp() is not 0 if a shell script, because
6120
         * /proc/<pid>/exe links a shell command (/bin/bash etc.) and the
6121
         * pname_status represents a shell script name. Then the full path
6122
         * of a shell script is taken from /proc/<pid>/cmdline.
6123
         */
6124
        ret = cg_get_procname_from_proc_cmdline(pid, pname_status, &pname_cmdline);
30✔
6125
        if (!ret) {
30✔
6126
                *procname = pname_cmdline;
18✔
6127
                free(pname_status);
18✔
6128
                return 0;
18✔
6129
        }
6130

6131
        /*
6132
         * The above strncmp() is not 0 also if executing a symbolic link,
6133
         * /proc/pid/exe points to real executable name then. Return it as
6134
         * the last resort.
6135
         */
6136
        free(pname_status);
12✔
6137
        *procname = strdup(buf);
12✔
6138
        if (*procname == NULL) {
12✔
6139
                last_errno = errno;
×
6140
                return ECGOTHER;
×
6141
        }
6142

6143
        return 0;
12✔
6144
}
6145

6146
int cgroup_register_unchanged_process(pid_t pid, int flags)
4✔
6147
{
6148
        char buff[sizeof(CGRULE_SUCCESS_STORE_PID)];
6149
        struct sockaddr_un addr;
6150
        size_t ret_len;
6151
        int ret = 1;
4✔
6152
        int sk;
6153

6154
        sk = socket(PF_UNIX, SOCK_STREAM, 0);
4✔
6155
        if (sk < 0)
4✔
6156
                return 1;
×
6157

6158
        memset(&addr, 0, sizeof(addr));
4✔
6159
        addr.sun_family = AF_UNIX;
4✔
6160
        strcpy(addr.sun_path, CGRULE_CGRED_SOCKET_PATH);
4✔
6161

6162
        if (connect(sk, (struct sockaddr *)&addr,
4✔
6163
            sizeof(addr.sun_family) + strlen(CGRULE_CGRED_SOCKET_PATH)) < 0) {
6164
                /* If the daemon does not work, this function returns 0 as success. */
6165
                ret = 0;
4✔
6166
                goto close;
4✔
6167
        }
6168
        if (write(sk, &pid, sizeof(pid)) < 0)
×
6169
                goto close;
×
6170

6171
        if (write(sk, &flags, sizeof(flags)) < 0)
×
6172
                goto close;
×
6173

6174
        ret_len = read(sk, buff, sizeof(buff));
×
6175
        if (ret_len != sizeof(buff))
×
6176
                goto close;
×
6177

6178
        if (strncmp(buff, CGRULE_SUCCESS_STORE_PID, sizeof(buff)))
×
6179
                goto close;
×
6180

6181
        ret = 0;
×
6182
close:
4✔
6183
        close(sk);
4✔
6184

6185
        return ret;
4✔
6186
}
6187

6188
int cgroup_get_subsys_mount_point(const char *controller, char **mount_point)
×
6189
{
6190
        int ret = ECGROUPNOTEXIST;
×
6191
        int i;
6192

6193
        if (!cgroup_initialized)
×
6194
                return ECGROUPNOTINITIALIZED;
×
6195

6196
        if (!controller)
×
6197
                return ECGINVAL;
×
6198

6199
        pthread_rwlock_rdlock(&cg_mount_table_lock);
×
6200
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
×
6201
                if (strncmp(cg_mount_table[i].name, controller, FILENAME_MAX))
×
6202
                        continue;
×
6203

6204
                *mount_point = strdup(cg_mount_table[i].mount.path);
×
6205

6206
                if (!*mount_point) {
×
6207
                        last_errno = errno;
×
6208
                        ret = ECGOTHER;
×
6209
                        goto out_exit;
×
6210
                }
6211

6212
                ret = 0;
×
6213
                break;
×
6214
        }
6215
out_exit:
×
6216
        pthread_rwlock_unlock(&cg_mount_table_lock);
×
6217

6218
        return ret;
×
6219
}
6220

6221
int cgroup_get_all_controller_end(void **handle)
180✔
6222
{
6223
        FILE *proc_cgrp = (FILE *) *handle;
180✔
6224

6225
        if (!proc_cgrp)
180✔
6226
                return ECGINVAL;
×
6227

6228
        fclose(proc_cgrp);
180✔
6229
        *handle = NULL;
180✔
6230

6231
        return 0;
180✔
6232
}
6233

6234
int cgroup_get_all_controller_next(void **handle, struct controller_data *info)
2,700✔
6235
{
6236
        FILE *proc_cgrp = (FILE *) *handle;
2,700✔
6237
        int hierarchy, num_cgrps, enabled;
6238
        char subsys_name[FILENAME_MAX];
6239
        int err = 0;
2,700✔
6240

6241
        if (!proc_cgrp)
2,700✔
6242
                return ECGINVAL;
×
6243

6244
        if (!info)
2,700✔
6245
                return ECGINVAL;
×
6246

6247
        /*
6248
         * check Linux Kernel sources/kernel/cgroup/cgroup.c cgroup_init_early(),
6249
         * MAX_CGROUP_TYPE_NAMELEN check for details on why 32 is used.
6250
         */
6251
        err = fscanf(proc_cgrp, "%32s %d %d %d\n", subsys_name, &hierarchy, &num_cgrps,
2,700✔
6252
                     &enabled);
6253

6254
        if (err != 4)
2,700✔
6255
                return ECGEOF;
180✔
6256

6257
        strncpy(info->name, subsys_name, FILENAME_MAX);
2,520✔
6258
        info->name[FILENAME_MAX-1] = '\0';
2,520✔
6259
        info->hierarchy = hierarchy;
2,520✔
6260
        info->num_cgroups = num_cgrps;
2,520✔
6261
        info->enabled = enabled;
2,520✔
6262

6263
        return 0;
2,520✔
6264
}
6265

6266
int cgroup_get_all_controller_begin(void **handle, struct controller_data *info)
180✔
6267
{
6268
        FILE *proc_cgroup = NULL;
180✔
6269
        char buf[FILENAME_MAX];
6270
        int ret;
6271

6272
        if (!info)
180✔
6273
                return ECGINVAL;
×
6274

6275
        proc_cgroup = fopen("/proc/cgroups", "re");
180✔
6276
        if (!proc_cgroup) {
180✔
6277
                last_errno = errno;
×
6278
                return ECGOTHER;
×
6279
        }
6280

6281
        if (!fgets(buf, FILENAME_MAX, proc_cgroup)) {
180✔
6282
                last_errno = errno;
×
6283
                fclose(proc_cgroup);
×
6284
                *handle = NULL;
×
6285
                return ECGOTHER;
×
6286
        }
6287
        *handle = proc_cgroup;
180✔
6288

6289
        ret = cgroup_get_all_controller_next(handle, info);
180✔
6290
        if (ret != 0) {
180✔
6291
                fclose(proc_cgroup);
×
6292
                *handle = NULL;
×
6293
        }
6294

6295
        return ret;
180✔
6296
}
6297

6298
static int pid_compare(const void *a, const void *b)
84✔
6299
{
6300
        const pid_t *pid1, *pid2;
6301

6302
        pid1 = (pid_t *) a;
84✔
6303
        pid2 = (pid_t *) b;
84✔
6304

6305
        return (*pid1 - *pid2);
84✔
6306
}
6307

6308
/* pids needs to be completely uninitialized so that we can set it up
6309
 *
6310
 * Caller must free up pids.
6311
 */
6312
static int read_pids(char *path, pid_t **pids, int *size)
10✔
6313
{
6314
        int tot_pids = 16;
10✔
6315
        pid_t *tmp_list;
6316
        FILE *pid_file;
6317
        int n = 0;
10✔
6318
        int err;
6319

6320
        pid_file = fopen(path, "r");
10✔
6321
        if (!pid_file) {
10✔
6322
                last_errno = errno;
×
6323
                *pids = NULL;
×
6324
                *size = 0;
×
6325
                if (errno == ENOENT)
×
6326
                        return ECGROUPUNSUPP;
×
6327
                else
6328
                        return ECGOTHER;
×
6329
        }
6330

6331
        /* Keep doubling the memory allocated if needed */
6332
        tmp_list = malloc(sizeof(pid_t) * tot_pids);
10✔
6333
        if (!tmp_list) {
10✔
6334
                last_errno = errno;
×
6335
                fclose(pid_file);
×
6336
                return ECGOTHER;
×
6337
        }
6338

6339
        while (!feof(pid_file)) {
22✔
6340
                while (!feof(pid_file) && n < tot_pids) {
62✔
6341
                        pid_t pid;
6342

6343
                        err = fscanf(pid_file, "%u", &pid);
60✔
6344
                        if (err == EOF)
60✔
6345
                                break;
10✔
6346
                        tmp_list[n] = pid;
50✔
6347
                        n++;
50✔
6348
                }
6349
                if (!feof(pid_file)) {
12✔
6350
                        pid_t *orig_list = tmp_list;
2✔
6351

6352
                        tot_pids *= 2;
2✔
6353
                        tmp_list = realloc(tmp_list, sizeof(pid_t) * tot_pids);
2✔
6354
                        if (!tmp_list) {
2✔
6355
                                last_errno = errno;
×
6356
                                fclose(pid_file);
×
6357
                                free(orig_list);
×
6358
                                *pids = NULL;
×
6359
                                *size = 0;
×
6360
                                return ECGOTHER;
×
6361
                        }
6362
                }
6363
        }
6364
        fclose(pid_file);
10✔
6365

6366
        *size = n;
10✔
6367
        qsort(tmp_list, n, sizeof(pid_t), &pid_compare);
10✔
6368
        *pids = tmp_list;
10✔
6369

6370
        return 0;
10✔
6371
}
6372

6373
int cgroup_get_procs(const char *name, const char *controller, pid_t **pids, int *size)
10✔
6374
{
6375
        char cgroup_path[FILENAME_MAX];
6376

6377
        cg_build_path(name, cgroup_path, controller);
10✔
6378
        strncat(cgroup_path, "/cgroup.procs", FILENAME_MAX-strlen(cgroup_path));
10✔
6379

6380
        return read_pids(cgroup_path, pids, size);
10✔
6381
}
6382

6383
int cgroup_get_threads(const char *name, const char *controller, pid_t **pids, int *size)
×
6384
{
6385
        char cgroup_path[FILENAME_MAX];
6386

6387
        cg_build_path(name, cgroup_path, controller);
×
6388
        strncat(cgroup_path, "/cgroup.threads", FILENAME_MAX-strlen(cgroup_path));
×
6389

6390
        return read_pids(cgroup_path, pids, size);
×
6391
}
6392

6393
int cgroup_dictionary_create(struct cgroup_dictionary **dict,
13✔
6394
                             int flags)
6395
{
6396
        if (!dict)
13✔
6397
                return ECGINVAL;
×
6398

6399
        *dict = (struct cgroup_dictionary *) calloc(1, sizeof(struct cgroup_dictionary));
13✔
6400
        if (!*dict) {
13✔
6401
                last_errno = errno;
×
6402
                return ECGOTHER;
×
6403
        }
6404
        (*dict)->flags = flags;
13✔
6405

6406
        return 0;
13✔
6407
}
6408

6409
int cgroup_dictionary_add(struct cgroup_dictionary *dict, const char *name, const char *value)
26✔
6410
{
6411
        struct cgroup_dictionary_item *it;
6412

6413
        if (!dict)
26✔
6414
                return ECGINVAL;
×
6415

6416
        it = (struct cgroup_dictionary_item *) malloc(sizeof(struct cgroup_dictionary_item));
26✔
6417
        if (!it) {
26✔
6418
                last_errno = errno;
×
6419
                return ECGOTHER;
×
6420
        }
6421

6422
        it->next = NULL;
26✔
6423
        it->name = name;
26✔
6424
        it->value = value;
26✔
6425

6426
        if (dict->tail) {
26✔
6427
                dict->tail->next = it;
13✔
6428
                dict->tail = it;
13✔
6429
        } else {
6430
                /* It is the first item */
6431
                dict->tail = it;
13✔
6432
                dict->head = it;
13✔
6433
        }
6434

6435
        return 0;
26✔
6436
}
6437

6438
int cgroup_dictionary_free(struct cgroup_dictionary *dict)
18✔
6439
{
6440
        struct cgroup_dictionary_item *it;
6441

6442
        if (!dict)
18✔
6443
                return ECGINVAL;
5✔
6444

6445
        it = dict->head;
13✔
6446
        while (it) {
39✔
6447
                struct cgroup_dictionary_item *del = it;
26✔
6448

6449
                it = it->next;
26✔
6450
                if (!(dict->flags & CG_DICT_DONT_FREE_ITEMS)) {
26✔
6451
                        free((void *)del->value);
26✔
6452
                        free((void *)del->name);
26✔
6453
                }
6454
                free(del);
26✔
6455
        }
6456
        free(dict);
13✔
6457

6458
        return 0;
13✔
6459
}
6460

6461
int cgroup_dictionary_iterator_begin(struct cgroup_dictionary *dict, void **handle,
13✔
6462
                                     const char **name, const char **value)
6463
{
6464
        struct cgroup_dictionary_iterator *iter;
6465

6466
        *handle = NULL;
13✔
6467

6468
        if (!dict)
13✔
6469
                return ECGINVAL;
×
6470

6471
        iter = (struct cgroup_dictionary_iterator *)
6472
                malloc(sizeof(struct cgroup_dictionary_iterator));
13✔
6473
        if (!iter) {
13✔
6474
                last_errno = errno;
×
6475
                return ECGOTHER;
×
6476
        }
6477

6478
        iter->item = dict->head;
13✔
6479
        *handle = iter;
13✔
6480

6481
        return cgroup_dictionary_iterator_next(handle, name, value);
13✔
6482
}
6483

6484
int cgroup_dictionary_iterator_next(void **handle, const char **name, const char **value)
39✔
6485
{
6486
        struct cgroup_dictionary_iterator *iter;
6487

6488
        if (!handle)
39✔
6489
                return ECGINVAL;
×
6490

6491
        iter = *handle;
39✔
6492

6493
        if (!iter)
39✔
6494
                return ECGINVAL;
×
6495

6496
        if (!iter->item)
39✔
6497
                return ECGEOF;
13✔
6498

6499
        *name = iter->item->name;
26✔
6500
        *value = iter->item->value;
26✔
6501
        iter->item = iter->item->next;
26✔
6502

6503
        return 0;
26✔
6504
}
6505

6506
void cgroup_dictionary_iterator_end(void **handle)
13✔
6507
{
6508
        if (!handle)
13✔
6509
                return;
×
6510

6511
        free(*handle);
13✔
6512
        *handle = NULL;
13✔
6513
}
6514

6515
int cgroup_get_subsys_mount_point_begin(const char *controller, void **handle, char *path)
×
6516
{
6517
        int i;
6518

6519
        if (!cgroup_initialized)
×
6520
                return ECGROUPNOTINITIALIZED;
×
6521
        if (!handle || !path || !controller)
×
6522
                return ECGINVAL;
×
6523

6524
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++)
×
6525
                if (strcmp(controller, cg_mount_table[i].name) == 0)
×
6526
                        break;
×
6527

6528
        if (cg_mount_table[i].name[0] == '\0') {
×
6529
                /* The controller is not mounted at all */
6530
                *handle = NULL;
×
6531
                *path = '\0';
×
6532
                return ECGEOF;
×
6533
        }
6534

6535
        /*
6536
         * 'handle' is pointer to struct cg_mount_point, which should be
6537
         * returned next.
6538
         */
6539
        *handle = cg_mount_table[i].mount.next;
×
6540
        strcpy(path, cg_mount_table[i].mount.path);
×
6541

6542
        return 0;
×
6543
}
6544

6545
int cgroup_get_subsys_mount_point_next(void **handle, char *path)
×
6546
{
6547
        struct cg_mount_point *it;
6548

6549
        if (!cgroup_initialized)
×
6550
                return ECGROUPNOTINITIALIZED;
×
6551

6552
        if (!handle || !path)
×
6553
                return ECGINVAL;
×
6554

6555
        it = *handle;
×
6556
        if (!it) {
×
6557
                *handle = NULL;
×
6558
                *path = '\0';
×
6559
                return ECGEOF;
×
6560
        }
6561

6562
        *handle = it->next;
×
6563
        strcpy(path, it->path);
×
6564

6565
        return 0;
×
6566
}
6567

6568
int cgroup_get_subsys_mount_point_end(void **handle)
×
6569
{
6570
        if (!cgroup_initialized)
×
6571
                return ECGROUPNOTINITIALIZED;
×
6572

6573
        if (!handle)
×
6574
                return ECGINVAL;
×
6575

6576
        *handle = NULL;
×
6577

6578
        return 0;
×
6579
}
6580

6581
int cgroup_get_controller_version(const char * const controller, enum cg_version_t * const version)
3,212✔
6582
{
6583
        int i;
6584

6585
        if (!version)
3,212✔
6586
                return ECGINVAL;
×
6587

6588
        if (!controller && strlen(cg_cgroup_v2_mount_path) > 0) {
3,212✔
6589
                *version = CGROUP_V2;
56✔
6590
                return 0;
56✔
6591
        }
6592

6593
        if (!controller)
3,156✔
6594
                return ECGINVAL;
×
6595

6596
        *version = CGROUP_UNK;
3,156✔
6597

6598
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
9,027✔
6599
                if (strncmp(cg_mount_table[i].name, controller,
9,024✔
6600
                            sizeof(cg_mount_table[i].name)) == 0) {
6601
                        *version = cg_mount_table[i].version;
3,153✔
6602
                        return 0;
3,153✔
6603
                }
6604
        }
6605

6606
        return ECGROUPNOTEXIST;
3✔
6607
}
6608

6609
static int search_and_append_mnt_path(struct cg_mount_point **mount_point, char *path)
18✔
6610
{
6611
        struct cg_mount_point *mnt_point, *mnt_tmp, *mnt_prev;
6612

6613
        mnt_tmp = *mount_point;
18✔
6614
        while (mnt_tmp) {
18✔
6615
                if (strcmp(mnt_tmp->path, path) == 0)
16✔
6616
                        return ECGVALUEEXISTS;
16✔
6617

6618
                mnt_prev = mnt_tmp;
×
6619
                mnt_tmp = mnt_tmp->next;
×
6620
        }
6621

6622
        mnt_point = malloc(sizeof(struct cg_mount_point));
2✔
6623
        if (mnt_point == NULL) {
2✔
6624
                last_errno = errno;
×
6625
                return ECGOTHER;
×
6626
        }
6627

6628
        strncpy(mnt_point->path, path, FILENAME_MAX - 1);
2✔
6629
        mnt_point->path[FILENAME_MAX - 1] = '\0';
2✔
6630

6631
        mnt_point->next = NULL;
2✔
6632

6633
        if (*mount_point == NULL)
2✔
6634
                *mount_point = mnt_point;
2✔
6635
        else
6636
                mnt_prev->next = mnt_point;
×
6637

6638
        return 0;
2✔
6639
}
6640

6641
/**
6642
 * List the mount paths, that matches the specified version
6643
 *
6644
 *        @param cgrp_version The cgroup type/version
6645
 *        @param mount_paths Holds the list of mount paths
6646
 *        @return 0 success and list of mounts paths in mount_paths
6647
 *                ECGOTHER on failure and mount_paths is NULL.
6648
 */
6649
int cgroup_list_mount_points(const enum cg_version_t cgrp_version, char ***mount_paths)
4✔
6650
{
6651
        struct cg_mount_point *mount_point, *mnt_tmp = NULL;
4✔
6652
        char **mnt_paths = NULL;
4✔
6653
        int i, idx = 0;
4✔
6654
        int ret;
6655

6656
        if (!cgroup_initialized)
4✔
6657
                return ECGROUPNOTINITIALIZED;
×
6658

6659
        if (cgrp_version != CGROUP_V1 && cgrp_version != CGROUP_V2)
4✔
6660
                return ECGINVAL;
×
6661

6662
        pthread_rwlock_rdlock(&cg_mount_table_lock);
4✔
6663

6664
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
40✔
6665
                if (cg_mount_table[i].version != cgrp_version)
36✔
6666
                        continue;
18✔
6667

6668
                mount_point = &cg_mount_table[i].mount;
18✔
6669
                while (mount_point) {
36✔
6670
                        ret = search_and_append_mnt_path(&mnt_tmp, mount_point->path);
18✔
6671
                        if (ret != 0 && ret != ECGVALUEEXISTS)
18✔
6672
                                goto err;
×
6673

6674
                        /* Avoid adding duplicate mount points */
6675
                        if (ret != ECGVALUEEXISTS)
18✔
6676
                                idx++;
2✔
6677

6678
                        mount_point = mount_point->next;
18✔
6679
                }
6680
        }
6681

6682
        /*
6683
         * Cgroup v2 can be mounted without any controller and these mount
6684
         * paths are not part of the cg_mount_table.  Check and append
6685
         * them to mnt_paths.
6686
         */
6687
        if (cgrp_version == CGROUP_V2 && cg_cgroup_v2_empty_mount_paths) {
4✔
6688
                mount_point = cg_cgroup_v2_empty_mount_paths;
×
6689
                while (mount_point) {
×
6690
                        ret = search_and_append_mnt_path(&mnt_tmp, mount_point->path);
×
6691
                        if (ret)
×
6692
                                goto err;
×
6693

6694
                        idx++;
×
6695
                        mount_point = mount_point->next;
×
6696
                }
6697
        }
6698

6699
        mnt_paths = malloc(sizeof(char *) * (idx + 1));
4✔
6700
        if (mnt_paths == NULL) {
4✔
6701
                last_errno = errno;
×
6702
                ret = ECGOTHER;
×
6703
                goto err;
×
6704
        }
6705

6706
        for (i = 0, mount_point = mnt_tmp;
4✔
6707
             mount_point;
6✔
6708
             mount_point = mount_point->next, i++) {
2✔
6709

6710
                mnt_paths[i] = strdup(mount_point->path);
2✔
6711
                if (mnt_paths[i] == NULL) {
2✔
6712
                        last_errno = errno;
×
6713
                        ret = ECGOTHER;
×
6714
                        goto err;
×
6715
                }
6716
        }
6717
        mnt_paths[i] = '\0';
4✔
6718

6719
        ret = 0;
4✔
6720
        *mount_paths = mnt_paths;
4✔
6721

6722
err:
4✔
6723
        pthread_rwlock_unlock(&cg_mount_table_lock);
4✔
6724

6725
        while (mnt_tmp) {
6✔
6726
                mount_point = mnt_tmp;
2✔
6727
                mnt_tmp = mnt_tmp->next;
2✔
6728
                free(mount_point);
2✔
6729
        }
6730

6731
        if (ret != 0 && mnt_paths) {
4✔
6732
                for (i = 0; i < idx; i++)
×
6733
                        free(mnt_paths[i]);
×
6734
                free(mnt_paths);
×
6735
                *mount_paths = NULL;
×
6736
        }
6737

6738
        return ret;
4✔
6739
}
6740

6741
const struct cgroup_library_version *cgroup_version(void)
4✔
6742
{
6743
        return &library_version;
4✔
6744
}
6745

6746
/**
6747
 * Finds the current cgroup setup mode (legacy/unified/hybrid).
6748
 * Returns unknown of failure and setup mode on success.
6749
 */
6750
enum cg_setup_mode_t cgroup_setup_mode(void)
196✔
6751
{
6752
#define CGROUP2_SUPER_MAGIC        0x63677270
6753
#define CGROUP_SUPER_MAGIC        0x27E0EB
6754

6755
        unsigned int cg_setup_mode_bitmask = 0U;
196✔
6756
        enum cg_setup_mode_t setup_mode;
6757
        struct statfs cgrp_buf;
6758
        int i, ret = 0;
196✔
6759

6760
        if (!cgroup_initialized)
196✔
6761
                return ECGROUPNOTINITIALIZED;
×
6762

6763
        setup_mode = CGROUP_MODE_UNK;
196✔
6764

6765
        pthread_rwlock_wrlock(&cg_mount_table_lock);
196✔
6766
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
1,960✔
6767
                ret = statfs(cg_mount_table[i].mount.path, &cgrp_buf);
1,764✔
6768
                if (ret) {
1,764✔
6769
                        setup_mode = CGROUP_MODE_UNK;
×
6770
                        cgroup_err("Failed to get stats of '%s'\n", cg_mount_table[i].mount.path);
×
6771
                        goto out;
×
6772
                }
6773

6774
                if (cgrp_buf.f_type == CGROUP2_SUPER_MAGIC)
1,764✔
6775
                        cg_setup_mode_bitmask |= (1U << 0);
1,764✔
6776
                else if (cgrp_buf.f_type == CGROUP_SUPER_MAGIC)
×
6777
                        cg_setup_mode_bitmask |= (1U << 1);
×
6778
        }
6779

6780
        if (cg_cgroup_v2_empty_mount_paths)
196✔
6781
                cg_setup_mode_bitmask |= (1U << 0);
×
6782

6783
        if (cg_setup_mode_bitmask & (1U << 0) && cg_setup_mode_bitmask & (1U << 1))
196✔
6784
                setup_mode = CGROUP_MODE_HYBRID;
×
6785
        else if (cg_setup_mode_bitmask & (1U << 0))
196✔
6786
                setup_mode = CGROUP_MODE_UNIFIED;
196✔
6787
        else if (cg_setup_mode_bitmask & (1U << 1))
×
6788
                setup_mode = CGROUP_MODE_LEGACY;
×
6789

6790
out:
×
6791
        pthread_rwlock_unlock(&cg_mount_table_lock);
196✔
6792
        return setup_mode;
196✔
6793
}
6794

6795
int cgroup_get_controller_count(struct cgroup *cgrp)
25✔
6796
{
6797
        if (!cgrp)
25✔
6798
                return -1;
×
6799

6800
        return cgrp->index;
25✔
6801
}
6802

6803
struct cgroup_controller *cgroup_get_controller_by_index(struct cgroup *cgrp, int index)
83✔
6804
{
6805
        if (!cgrp)
83✔
6806
                return NULL;
×
6807

6808
        if (index >= cgrp->index)
83✔
6809
                return NULL;
×
6810

6811
        return cgrp->controller[index];
83✔
6812
}
6813

6814
char *cgroup_get_controller_name(struct cgroup_controller *controller)
83✔
6815
{
6816
        if (!controller)
83✔
6817
                return NULL;
×
6818

6819
        return controller->name;
83✔
6820
}
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