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

drakenclimber / libcgroup / 20820343591

08 Jan 2026 02:32PM UTC coverage: 56.067% (-0.2%) from 56.221%
20820343591

push

github

drakenclimber
src/api.c: potential null ptr deref in cg_get_cgroups_from_proc_cgroups

In function cg_get_cgroups_from_proc_cgroups there is an allocation of memory by calling
malloc(buff_len).
Result of this allocation is not checked,
and in case of OOM it'll lead to null ptr deref.

This commit adds handling of possible null ptr as a result of failed
memory allocation.

Found by Linux Verification Center (linuxtesting.org) with SVACE.

Signed-off-by: Mikhail Dmitrichenko <m.dmitrichenko222@gmail.com>
Acked-by: Kamalesh Babulal <kamalesh.babulal@oracle.com>
Signed-off-by: Tom Hromatka <tom.hromatka@oracle.com>
(cherry picked from commit 8a40bf6f6)

1 of 4 new or added lines in 1 file covered. (25.0%)

499 existing lines in 2 files now uncovered.

5577 of 9947 relevant lines covered (56.07%)

524.51 hits per line

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

61.72
/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)
13,025✔
144
{
145
        if (owner == NO_UID_GID)
13,025✔
146
                owner = getuid();
12,352✔
147
        if (group == NO_UID_GID)
13,025✔
148
                group = getgid();
12,352✔
149

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

157
        cgroup_dbg("chown: seeing file %s\n", filename);
13,024✔
158
        switch (ent->fts_info) {
13,024✔
159
        case FTS_ERR:
×
160
                errno = ent->fts_errno;
×
161
                break;
×
162
        case FTS_D:
13,024✔
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);
13,024✔
171
                break;
13,024✔
172
        }
173
        if (ret < 0) {
13,024✔
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;
13,024✔
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)
233✔
183
{
184
        int ret = 0;
233✔
185
        FTS *fts;
186

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

195
        while (1) {
13,024✔
196
                FTSENT *ent;
197

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

207
        return ret;
233✔
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,
13,024✔
256
                  int filem_change, int owner_is_umask)
257
{
258
        const char *filename = fts->fts_path;
13,024✔
259
        int ret = 0;
13,024✔
260

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

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

283
        return ret;
13,024✔
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,
233✔
291
                                         mode_t file_mode, int filem_change, int owner_is_umask,
292
                                         const char * const *ignore_list)
293
{
294
        int final_ret = 0;
233✔
295
        char *fts_path[2];
296
        int i, ignored;
297
        int ret = 0;
233✔
298
        FTS *fts;
299

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

304
        fts = fts_open(fts_path, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
233✔
305
        if (fts == NULL) {
233✔
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) {
13,024✔
312
                FTSENT *ent;
313

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

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

334
                ret = cg_chmod_file(fts, ent, dir_mode, dirm_change, file_mode, filem_change,
13,024✔
335
                                    owner_is_umask);
336
                if (ret) {
13,024✔
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);
233✔
343

344
        return final_ret;
233✔
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)
218✔
390
{
391
        char *tmp_string;
392
        char *base;
393

394
        tmp_string = strdup(path);
218✔
395

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

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

401
        free(tmp_string);
218✔
402

403
        return base;
218✔
404
}
405

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

410
        pthread_rwlock_rdlock(&cg_mount_table_lock);
715✔
411

412
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
1,659✔
413
                if (strncmp(cg_mount_table[i].name, name, sizeof(cg_mount_table[i].name)) == 0) {
1,659✔
414
                        pthread_rwlock_unlock(&cg_mount_table_lock);
644✔
415
                        return 1;
644✔
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,015✔
424
                    cg_mount_table[i].version == CGROUP_V2) {
71✔
425
                        pthread_rwlock_unlock(&cg_mount_table_lock);
71✔
426
                        return 1;
71✔
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)
1✔
490
{
491
        char *itr;
492

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

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

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

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

512
        return itr;
1✔
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
        if (!options) {
6✔
530
                cgroup_err("failed to parse options: (NULL)\n");
1✔
531
                return -EINVAL;
1✔
532
        }
533

534
        stok_buff = strtok(options, ",");
5✔
535
        if (!stok_buff) {
5✔
536
                cgroup_err("failed to parse options: %s\n", options);
1✔
537
                return -EINVAL;
1✔
538
        }
539

540
        do {
541
                cmp_len = min(strlen(stok_buff), strlen(CGRULE_OPTION_IGNORE));
5✔
542
                if (strlen(stok_buff) == strlen(CGRULE_OPTION_IGNORE) &&
5✔
543
                    strncmp(stok_buff, CGRULE_OPTION_IGNORE, cmp_len) == 0) {
4✔
544
                        rule->is_ignore |= CGRULE_OPT_IGNORE;
3✔
545
                        continue;
3✔
546
                }
547

548
                cmp_len = min(strlen(stok_buff), strlen(CGRULE_OPTION_IGNORE_RT));
2✔
549
                if (strlen(stok_buff) == strlen(CGRULE_OPTION_IGNORE_RT) &&
2✔
550
                    strncmp(stok_buff, CGRULE_OPTION_IGNORE_RT, cmp_len) == 0) {
×
551
                        rule->is_ignore |= CGRULE_OPT_IGNORE_RT;
×
552
                        continue;
×
553
                }
554

555
                cgroup_err("Unsupported option: %s\n", stok_buff);
2✔
556
                ret = -EINVAL;
2✔
557
                break;
2✔
558
        } while ((stok_buff = strtok(NULL, ",")));
3✔
559

560
        return ret;
4✔
561
}
562

563
STATIC int get_next_rule_field(char *rule, char *field, size_t field_len, bool expect_quotes)
30✔
564
{
565
        char *quote_start = NULL, *quote_end = NULL;
30✔
566
        char *tmp, *_rule = rule;
30✔
567
        int len = 0;
30✔
568

569
        if (!rule || !field)
30✔
570
                return ECGINVAL;
2✔
571

572
        /* trim the leading whitespace */
573
        while (*rule == ' ' || *rule == '\t')
48✔
574
                rule++;
20✔
575

576
        tmp = rule;
28✔
577

578
        while (*rule != ' ' && *rule != '\t' && *rule != '\n' && *rule != '\0') {
240✔
579
                if (*rule == '"') {
221✔
580
                        quote_start = rule;
9✔
581
                        rule++;
9✔
582
                        break;
9✔
583
                }
584
                rule++;
212✔
585
        }
586

587
        if (quote_start) {
28✔
588
                if (!expect_quotes)
9✔
589
                        return ECGINVAL;
1✔
590

591
                while (*rule != '"' && *rule != '\n' && *rule != '\0')
144✔
592
                        rule++;
136✔
593

594
                /* there should be a ending quote */
595
                if (*rule != '"')
8✔
596
                        return ECGINVAL;
1✔
597

598
                quote_end = rule;
7✔
599
        }
600

601
        if (quote_end) {
26✔
602
                /* copy until the ending quotes */
603
                len = quote_end - quote_start - 1;
7✔
604
                if (len >= field_len)
7✔
605
                        return ECGINVAL;
2✔
606

607
                strncpy(field, quote_start + 1, len);
5✔
608
                field[len] = '\0';
5✔
609
        } else {
610
                len = rule - tmp;
19✔
611
                if (len >= field_len)
19✔
612
                        return ECGINVAL;
1✔
613

614
                strncpy(field, tmp, len);
18✔
615
                field[len] = '\0';
18✔
616
        }
617

618
        len = (rule - _rule);
23✔
619

620
        /* count until the ending quotes */
621
        if (quote_start)
23✔
622
                len += 1;
5✔
623

624
        return len;
23✔
625
}
626

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

656
        /* Buffer to store the line we're working on */
657
        char buff[CGRP_RULE_MAXLINE] = { '\0' };
1✔
658

659
        /* Iterator for the line we're working on */
660
        char *itr = NULL;
1✔
661

662
        /* Pointer to process name in a line of the configuration file */
663
        char *procname = NULL;
1✔
664

665
        /* Pointer to the list that we're using */
666
        struct cgroup_rule_list *lst = NULL;
1✔
667

668
        /* Rule to add to the list */
669
        struct cgroup_rule *newrule = NULL;
1✔
670

671
        /* Structure to get GID from group name */
672
        struct group *grp;
673

674
        /* Structure to get UID from user name */
675
        struct passwd *pwd;
676

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

689
        /* The current line number */
690
        unsigned int linenum = 0;
1✔
691

692
        /* Did we skip the previous line? */
693
        bool skipped = false;
1✔
694

695
        /* Have we found a matching rule (non-cache mode)? */
696
        bool matched = false;
1✔
697

698
        /* Return codes */
699
        int ret = 0;
1✔
700

701
        /* Temporary buffer for strtok() */
702
        char *stok_buff = NULL;
1✔
703

704
        /* Loop variable. */
705
        int i = 0;
1✔
706

707
        /* Determine which list we're using. */
708
        if (cache)
1✔
709
                lst = &rl;
1✔
710
        else
711
                lst = &trl;
×
712

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

723
        /* Now, parse the configuration file one line at a time. */
724
        cgroup_dbg("Parsing configuration file %s.\n", filename);
1✔
725
        while (fgets(buff, sizeof(buff), fp) != NULL) {
2✔
726
                linenum++;
1✔
727

728
                itr = cg_skip_unused_charactors_in_rule(buff);
1✔
729
                if (!itr)
1✔
730
                        continue;
×
731

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

741
                /* clear the buffer. */
742
                grp = NULL;
1✔
743
                pwd = NULL;
1✔
744

745
                /*
746
                 * If there is something left, it should be a rule.  Otherwise,
747
                 * there's an error in the configuration file.
748
                 */
749
                skipped = false;
1✔
750

751
                ret = get_next_rule_field(itr, key, CGRP_RULE_MAXKEY, true);
1✔
752
                if (!ret) {
1✔
753
                        cgroup_err("failed to parse configuration file on line %d\n", linenum);
×
754
                        goto parsefail;
×
755
                }
756

757
                itr += ret;
1✔
758
                ret = get_next_rule_field(itr, controllers, CG_CONTROLLER_MAX, false);
1✔
759
                if (!ret) {
1✔
760
                        cgroup_err("failed to parse configuration file on line %d\n", linenum);
×
761
                        goto parsefail;
×
762
                }
763

764
                itr += ret;
1✔
765
                ret = get_next_rule_field(itr, destination, FILENAME_MAX, true);
1✔
766
                if (!ret) {
1✔
767
                        cgroup_err("failed to parse configuration file on line %d\n", linenum);
×
768
                        goto parsefail;
×
769
                }
770

771
                itr += ret;
1✔
772
                ret = get_next_rule_field(itr, options, CG_OPTIONS_MAX, false);
1✔
773
                if (!ret)
1✔
774
                        has_options = false;
1✔
775
                else
776
                        has_options = true;
×
777

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

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

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

859
                        for (i = 0; grp->gr_mem[i]; i++) {
×
860
                                if (!(strcmp(pwd->pw_name, grp->gr_mem[i])))
×
861
                                        matched = true;
×
862
                        }
863
                }
864

865
                if (uid == muid || gid == mgid || uid == CGRULE_WILD)
1✔
866
                        matched = true;
1✔
867

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

884
                                mproc_base = cgroup_basename(mprocname);
×
885
                                if (strcmp(mprocname, procname) && strcmp(mproc_base, procname)) {
×
886
                                        uid = CGRULE_INVALID;
×
887
                                        gid = CGRULE_INVALID;
×
888
                                        matched = false;
×
889
                                        free(mproc_base);
×
890
                                        continue;
×
891
                                }
892
                                free(mproc_base);
×
893
                        }
894
                }
895

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

909
                newrule->uid = uid;
1✔
910
                newrule->gid = gid;
1✔
911
                newrule->is_ignore = 0;
1✔
912

913
                len_username = min(len_username, sizeof(newrule->username) - 1);
1✔
914
                strncpy(newrule->username, user, len_username);
1✔
915
                newrule->username[sizeof(newrule->username) - 1] = '\0';
1✔
916

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

933
                if (has_options) {
1✔
934
                        ret = cgroup_parse_rules_options(options, newrule);
×
935
                        if (ret < 0)
×
936
                                goto destroyrule;
×
937
                }
938

939
                newrule->next = NULL;
1✔
940

941
                /* Parse the controller list, and add that to newrule too. */
942
                stok_buff = strtok(controllers, ",");
1✔
943
                if (!stok_buff) {
1✔
944
                        cgroup_err("failed to parse controllers on line %d\n", linenum);
×
945
                        goto destroyrule;
×
946
                }
947

948
                i = 0;
1✔
949
                do {
950
                        if (i >= MAX_MNT_ELEMENTS) {
1✔
951
                                cgroup_err("too many controllers listed on line %d\n", linenum);
×
952
                                goto destroyrule;
×
953
                        }
954

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

964
                /* Now, push the rule. */
965
                if (lst->head == NULL) {
1✔
966
                        lst->head = newrule;
1✔
967
                        lst->tail = newrule;
1✔
968
                } else {
969
                        lst->tail->next = newrule;
×
970
                        lst->tail = newrule;
×
971
                }
972

973
                cgroup_dbg("Added rule %s (UID: %d, GID: %d) -> %s for controllers:",
1✔
974
                           lst->tail->username, lst->tail->uid, lst->tail->gid,
975
                           lst->tail->destination);
976

977
                for (i = 0; i < MAX_MNT_ELEMENTS && lst->tail->controllers[i]; i++)
2✔
978
                        cgroup_dbg(" %s", lst->tail->controllers[i]);
1✔
979
                cgroup_dbg("\n");
1✔
980
        }
981

982
        /* If we make it here, there were no errors. */
983
        cgroup_dbg("Parsing of configuration file complete.\n\n");
1✔
984
        ret = (matched && !cache) ? -1 : 0;
1✔
985
        goto close;
1✔
986

987
destroyrule:
×
988
        cgroup_free_rule(newrule);
×
989

990
parsefail:
×
991
        ret = ECGRULESPARSEFAIL;
×
992

993
close:
1✔
994
        fclose(fp);
1✔
995
finish:
1✔
996
        return ret;
1✔
997
}
998

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

1035
        /* Directory variables */
1036
        const char *dirname = CGRULES_CONF_DIR;
1✔
1037
        struct dirent *item;
1038
        char *tmp;
1039
        int sret;
1040
        DIR *d;
1041

1042
        int ret;
1043

1044
        /* Determine which list we're using. */
1045
        if (cache)
1✔
1046
                lst = &rl;
1✔
1047
        else
1048
                lst = &trl;
×
1049

1050
        /* If our list already exists, clean it. */
1051
        if (lst->head)
1✔
1052
                cgroup_free_rule_list(lst);
×
1053

1054
        pthread_rwlock_wrlock(&rl_lock);
1✔
1055

1056
        /* Parse CGRULES_CONF_FILE configuration file (back compatibility). */
1057
        ret = cgroup_parse_rules_file(CGRULES_CONF_FILE, cache, muid, mgid, mprocname);
1✔
1058

1059
        /*
1060
         * if match (ret = -1), stop parsing other files,
1061
         * just return or ret > 0 => error
1062
         */
1063
        if (ret != 0) {
1✔
1064
                pthread_rwlock_unlock(&rl_lock);
×
1065
                return ret;
×
1066
        }
1067

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

1079
                return 0;
1✔
1080
        }
1081

1082
        /* Read all files from CGRULES_CONF_FILE_DIR */
1083
        do {
1084
                item = readdir(d);
×
1085
                if (item && (item->d_type == DT_REG || item->d_type == DT_LNK)) {
×
1086

1087
                        sret = asprintf(&tmp, "%s/%s", dirname, item->d_name);
×
1088
                        if (sret < 0) {
×
1089
                                cgroup_err("Out of memory\n");
×
1090

1091
                                /*
1092
                                 * Cannot read directory.
1093
                                 * However, CGRULES_CONF_FILE is successfully
1094
                                 * parsed. Thus return as a success for back
1095
                                 * compatibility.
1096
                                 */
1097
                                ret = 0;
×
1098
                                goto unlock_list;
×
1099
                        }
1100

1101
                        cgroup_dbg("Parsing cgrules file: %s\n", tmp);
×
1102
                        ret = cgroup_parse_rules_file(tmp, cache, muid, mgid, mprocname);
×
1103

1104
                        free(tmp);
×
1105

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

1121
unlock_list:
×
1122
        closedir(d);
×
1123
        pthread_rwlock_unlock(&rl_lock);
×
1124

1125
        return ret;
×
1126
}
1127

1128
int cg_add_duplicate_mount(struct cg_mount_table_s *item, const char *path)
8✔
1129
{
1130
        struct cg_mount_point *mount, *it;
1131

1132
        mount = malloc(sizeof(struct cg_mount_point));
8✔
1133
        if (!mount) {
8✔
1134
                last_errno = errno;
×
1135
                return ECGOTHER;
×
1136
        }
1137
        mount->next = NULL;
8✔
1138

1139
        strncpy(mount->path, path, sizeof(mount->path));
8✔
1140
        mount->path[sizeof(mount->path)-1] = '\0';
8✔
1141

1142
        /*
1143
         * Add the mount point to the end of the list. Assuming the list is
1144
         * short, no optimization is done.
1145
         */
1146
        it = &item->mount;
8✔
1147
        while (it->next)
8✔
1148
                it = it->next;
×
1149

1150
        it->next = mount;
8✔
1151

1152
        return 0;
8✔
1153
}
1154

1155
/*
1156
 * Tries to find if any controller in cg_mount_table have already mounted on
1157
 * the mount_path and if mounted sets the matching controller idx share_mnt
1158
 * flag and Return 1 or 0 otherwise.
1159
 */
1160
static int cgroup_set_cg_mnt_tbl_shared_mnt(char *mount_path, int *mnt_tbl_idx)
10,316✔
1161
{
1162
        int i, shared_mnt = 0;
10,316✔
1163

1164
        /* Check if controllers share mount points */
1165
        for  (i = 0; i < *mnt_tbl_idx; i++) {
30,022✔
1166
                if (strncmp(mount_path, cg_mount_table[i].mount.path, FILENAME_MAX) == 0) {
28,544✔
1167
                        cg_mount_table[i].shared_mnt = 1;
8,838✔
1168
                        shared_mnt = 1;
8,838✔
1169
                        break;
8,838✔
1170
                }
1171
        }
1172

1173
        return shared_mnt;
10,316✔
1174
}
1175

1176
static void cgroup_cg_mount_table_append(const char *name, const char *mount_path,
10,308✔
1177
                                         enum cg_version_t version, int *mnt_tbl_idx,
1178
                                         const char *mnt_opts, int shared_mnt)
1179
{
1180
        int i = *mnt_tbl_idx;
10,308✔
1181

1182
        strncpy(cg_mount_table[i].name,        name, CONTROL_NAMELEN_MAX);
10,308✔
1183
        cg_mount_table[i].name[CONTROL_NAMELEN_MAX-1] = '\0';
10,308✔
1184

1185
        strncpy(cg_mount_table[i].mount.path, mount_path, FILENAME_MAX);
10,308✔
1186
        cg_mount_table[i].mount.path[FILENAME_MAX-1] = '\0';
10,308✔
1187

1188
        cg_mount_table[i].shared_mnt = shared_mnt;
10,308✔
1189
        cg_mount_table[i].version = version;
10,308✔
1190
        cg_mount_table[i].mount.next = NULL;
10,308✔
1191

1192
        cgroup_dbg("Found cgroup option %s, count %d\n", mnt_opts, i);
10,308✔
1193

1194
        (*mnt_tbl_idx)++;
10,308✔
1195
}
10,308✔
1196

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

1212
        for (i = 0; controllers[i] != NULL; i++) {
5,471✔
1213
                mntopt = hasmntopt(ent, controllers[i]);
5,104✔
1214

1215
                if (!mntopt)
5,104✔
1216
                        continue;
5,102✔
1217

1218
                c = mntopt[strlen(controllers[i])];
2✔
1219

1220
                if (c != '\0' && c != ',')
2✔
1221
                        continue;
×
1222

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

1225
                /* Check if controllers share mount points */
1226
                shared_mnt = cgroup_set_cg_mnt_tbl_shared_mnt(ent->mnt_dir, mnt_tbl_idx);
2✔
1227

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

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

1247
                cgroup_cg_mount_table_append(controllers[i], ent->mnt_dir, CGROUP_V1, mnt_tbl_idx,
1✔
1248
                                             ent->mnt_opts, shared_mnt);
1✔
1249

1250
                if ((*mnt_tbl_idx) >= CG_CONTROLLER_MAX)
1✔
1251
                        goto out;
×
1252
        }
1253

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

1257
        if (mntopt) {
367✔
1258
                mntopt = strtok_r(mntopt, ",", &strtok_buffer);
365✔
1259
                if (!mntopt)
365✔
1260
                        goto out;
×
1261

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

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

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

1286
                cgroup_cg_mount_table_append(mntopt, ent->mnt_dir, CGROUP_V1, mnt_tbl_idx,
364✔
1287
                                             ent->mnt_opts, shared_mnt);
364✔
1288
        }
1289

1290
out:
2✔
1291
        return ret;
367✔
1292
}
1293

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

1309
        /*
1310
         * Save off this mount point.  This may be used later to
1311
         * build the cg_path.
1312
         */
1313
        strncpy(cg_cgroup_v2_mount_path, ent->mnt_dir, FILENAME_MAX-1);
1,107✔
1314
        cg_cgroup_v2_mount_path[FILENAME_MAX-1] = '\0';
1,107✔
1315

1316
        /* determine what v2 controllers are available on this mount */
1317
        snprintf(cgrp_controllers_path, FILENAME_MAX, "%s/%s", ent->mnt_dir, CGV2_CONTROLLERS_FILE);
1,107✔
1318
        fp = fopen(cgrp_controllers_path, "re");
1,107✔
1319
        if (!fp) {
1,107✔
1320
                ret = ECGOTHER;
×
1321
                goto out;
×
1322
        }
1323

1324
        ret_c = fgets(line, CGV2_CONTROLLERS_LL_MAX, fp);
1,107✔
1325
        if (ret_c == NULL) {
1,107✔
1326
                struct cg_mount_point *tmp, *t;
1327

1328
                ret = ECGEOF;
1✔
1329

1330
                tmp = malloc(sizeof(struct cg_mount_point));
1✔
1331
                if (tmp == NULL) {
1✔
1332
                        last_errno = errno;
×
1333
                        ret = ECGOTHER;
×
1334
                        goto out;
×
1335
                }
1336

1337
                strncpy(tmp->path, cg_cgroup_v2_mount_path, sizeof(tmp->path) - 1);
1✔
1338
                tmp->path[sizeof(tmp->path)-1] = '\0';
1✔
1339
                tmp->next = NULL;
1✔
1340

1341
                t = cg_cgroup_v2_empty_mount_paths;
1✔
1342
                if (t == NULL) {
1✔
1343
                        cg_cgroup_v2_empty_mount_paths = tmp;
1✔
1344
                        goto out;
1✔
1345
                }
1346

1347
                while (t->next != NULL)
×
1348
                        t = t->next;
×
1349
                t->next = tmp;
×
1350

1351
                goto out;
×
1352
        }
1353

1354
        /* Remove the trailing newline */
1355
        ret_c[strlen(ret_c) - 1] = '\0';
1,106✔
1356

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

1368
        sprintf(controllers, "%s %s", ret_c, CGRP_FILE_PREFIX);
1,106✔
1369

1370
        /*
1371
         * cgroup.controllers returns a list of available controllers in
1372
         * the following format:
1373
         *        cpuset cpu io memory pids rdma
1374
         */
1375
        controller = strtok_r(controllers, " ", &stok_buff);
1,106✔
1376
        do {
1377
                /* Check if controllers share mount points */
1378
                shared_mnt = cgroup_set_cg_mnt_tbl_shared_mnt(ent->mnt_dir, mnt_tbl_idx);
9,950✔
1379

1380
                /* Do not have duplicates in mount table */
1381
                duplicate = 0;
9,950✔
1382
                for  (i = 0; i < *mnt_tbl_idx; i++) {
49,736✔
1383
                        if (strncmp(cg_mount_table[i].name, controller, FILENAME_MAX) == 0) {
39,793✔
1384
                                duplicate = 1;
7✔
1385
                                break;
7✔
1386
                        }
1387
                }
1388

1389
                if (duplicate) {
9,950✔
1390
                        cgroup_dbg("controller %s is already mounted on %s\n", controller,
7✔
1391
                                   cg_mount_table[i].mount.path);
1392

1393
                        ret = cg_add_duplicate_mount(&cg_mount_table[i], ent->mnt_dir);
7✔
1394
                        if (ret)
7✔
1395
                                break;
×
1396

1397
                        continue;
7✔
1398
                }
1399

1400
                /* This controller is not in the mount table.  Add it */
1401
                cgroup_cg_mount_table_append(controller, ent->mnt_dir, CGROUP_V2, mnt_tbl_idx,
9,943✔
1402
                                             controller, shared_mnt);
1403

1404
                if ((*mnt_tbl_idx) >= CG_CONTROLLER_MAX)
9,943✔
1405
                        goto out;
×
1406
        } while ((controller = strtok_r(NULL, " ", &stok_buff)));
9,950✔
1407

1408
out:
1,106✔
1409
        if (fp)
1,107✔
1410
                fclose(fp);
1,107✔
1411

1412
        if (controllers)
1,107✔
1413
                free(controllers);
1,106✔
1414

1415
        return ret;
1,107✔
1416
}
1417

1418
/*
1419
 * Free global variables filled by previous cgroup_init(). This function
1420
 * should be called with cg_mount_table_lock taken.
1421
 */
1422
static void cgroup_free_cg_mount_table(void)
1,104✔
1423
{
1424
        struct cg_mount_point *mount, *tmp;
1425
        int i;
1426

1427
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
4,491✔
1428
                mount = cg_mount_table[i].mount.next;
3,387✔
1429

1430
                while (mount) {
3,387✔
1431
                        tmp = mount;
×
1432
                        mount = mount->next;
×
1433
                        free(tmp);
×
1434
                }
1435
        }
1436

1437
        memset(&cg_mount_table, 0, sizeof(cg_mount_table));
1,104✔
1438
        memset(&cg_cgroup_v2_mount_path, 0, sizeof(cg_cgroup_v2_mount_path));
1,104✔
1439
        memset(&cg_cgroup_v2_empty_mount_paths, 0, sizeof(cg_cgroup_v2_empty_mount_paths));
573✔
1440
}
1,104✔
1441

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

1460
        if (!mnt || !mnt_opt || !is_set)
×
1461
                return ECGINVAL;
×
1462

1463
        proc_mount = setmntent(mnt, "r");
×
1464
        if (!proc_mount) {
×
1465
                cgroup_err("cannot open %s: %s\n", mnt, strerror(errno));
×
1466
                last_errno = errno;
×
1467
                ret = ECGOTHER;
×
1468
                goto err;
×
1469
        }
1470

1471
        temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
×
1472
        if (!temp_ent) {
×
1473
                last_errno = errno;
×
1474
                ret = ECGOTHER;
×
1475
                goto err;
×
1476
        }
1477

1478
        ent = getmntent_r(proc_mount, temp_ent,        mntent_buffer, sizeof(mntent_buffer));
×
1479
        if (!ent) {
×
1480
                last_errno = errno;
×
1481
                ret = ECGOTHER;
×
1482
                goto err;
×
1483
        }
1484

1485
        *is_set = 0;
×
1486
        while ((mntopt = hasmntopt(ent, mnt_opt))) {
×
1487
                mnt_opt_delim = mntopt[strlen(mnt_opt)];
×
1488
                if (mnt_opt_delim == '\0' || mnt_opt_delim == ',') {
×
1489
                        *is_set  = 1;
×
1490
                        break;
×
1491
                }
1492
        }
1493

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

1497
err:
×
1498
        if (proc_mount)
×
1499
                endmntent(proc_mount);
×
1500

1501
        if (temp_ent)
×
1502
                free(temp_ent);
×
1503

1504
        return ret;
×
1505
}
1506

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

1522
        proc_cgrp = fopen("/proc/cgroups", "re");
1,104✔
1523
        if (!proc_cgrp) {
1,104✔
1524
                cgroup_warn("cannot open /proc/cgroups: %s\n", strerror(errno));
×
1525
                ret = check_mount_point_opt("/proc/self/mounts", mnt_opt, &mnt_opt_set);
×
1526
                if (ret)
×
1527
                        goto err;
×
1528

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

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

1549
        if (!fgets(buf, CGV2_CONTROLLERS_LL_MAX, proc_cgrp)) {
1,104✔
1550
                cgroup_err("cannot read /proc/cgroups: %s\n", strerror(errno));
×
1551
                last_errno = errno;
×
1552
                ret = ECGOTHER;
×
1553
                goto err;
×
1554
        }
1555

1556
        while (!feof(proc_cgrp)) {
16,560✔
1557
                /*
1558
                 * check Linux Kernel sources/kernel/cgroup/cgroup.c cgroup_init_early(),
1559
                 * MAX_CGROUP_TYPE_NAMELEN check for details on why 32 is used.
1560
                 */
1561
                err = fscanf(proc_cgrp, "%32s %d %d %d", subsys_name, &hierarchy, &num_cgrps,
16,560✔
1562
                             &enabled);
1563
                if (err < 0)
16,560✔
1564
                        break;
1,104✔
1565

1566
                controllers[i] = strdup(subsys_name);
15,456✔
1567
                if (controllers[i] == NULL) {
15,456✔
1568
                        last_errno = errno;
×
1569
                        ret = ECGOTHER;
×
1570
                        break;
×
1571
                }
1572
                i++;
15,456✔
1573
        }
1574

1575
err:
×
1576
        if (proc_cgrp)
1,104✔
1577
                fclose(proc_cgrp);
1,104✔
1578

1579
        if (buf)
1,104✔
1580
                free(buf);
1,104✔
1581

1582
        if (ret != 0) {
1,104✔
1583
                for (i = 0; controllers[i]; i++) {
×
1584
                        free(controllers[i]);
×
1585
                        controllers[i] = NULL;
×
1586
                }
1587
        }
1588

1589
        return ret;
1,104✔
1590
}
1591

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

1605
        proc_mount = fopen("/proc/self/mounts", "re");
1,104✔
1606
        if (proc_mount == NULL) {
1,104✔
1607
                cgroup_err("cannot open /proc/self/mounts: %s\n", strerror(errno));
×
1608
                last_errno = errno;
×
1609
                ret = ECGOTHER;
×
1610
                goto err;
×
1611
        }
1612

1613
        temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
1,104✔
1614
        if (!temp_ent) {
1,104✔
1615
                last_errno = errno;
×
1616
                ret = ECGOTHER;
×
1617
                goto err;
×
1618
        }
1619

1620
        while ((ent = getmntent_r(proc_mount, temp_ent,        mntent_buffer,
37,561✔
1621
                                  sizeof(mntent_buffer))) != NULL) {
37,561✔
1622

1623
                if (strcmp(ent->mnt_type, "cgroup") == 0) {
36,461✔
1624
                        if (controllers[0] == NULL) {
364✔
1625
                                cgroup_err("cgroup v1 requires /proc/cgroups, check if /proc ");
×
1626
                                cgroup_cont("is mounted with subset=pid option.\n");
×
1627
                                ret = ECGINVAL;
×
1628
                                goto err;
×
1629
                        }
1630

1631
                        ret = cgroup_process_v1_mnt(controllers, ent, &found_mnt);
364✔
1632
                        if (ret)
364✔
1633
                                goto err;
×
1634

1635
                        if (found_mnt >= CG_CONTROLLER_MAX)
364✔
1636
                                break;
4✔
1637

1638
                        continue;
360✔
1639
                }
1640

1641
                if (strcmp(ent->mnt_type, "cgroup2") == 0) {
36,097✔
1642
                        ret = cgroup_process_v2_mnt(ent, &found_mnt);
1,104✔
1643
                        if (ret == ECGEOF) {
1,104✔
1644
                        /* The controllers file was empty.  Ignore and move on. */
1645
                                ret = 0;
×
1646
                                continue;
×
1647
                        }
1648

1649
                        if (ret)
1,104✔
1650
                                goto err;
×
1651

1652
                        if (found_mnt >= CG_CONTROLLER_MAX)
1,104✔
1653
                                break;
×
1654
                }
1655
        }
1656

1657
        if (!found_mnt)
1,104✔
1658
                ret = ECGROUPNOTMOUNTED;
×
1659

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

1671
err:
1,100✔
1672
        if (proc_mount)
1,104✔
1673
                fclose(proc_mount);
1,104✔
1674

1675
        if (temp_ent)
1,104✔
1676
                free(temp_ent);
1,104✔
1677

1678
        return ret;
1,104✔
1679
}
1680

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

1694
        cgroup_set_default_logger(-1);
1,104✔
1695

1696
        pthread_rwlock_wrlock(&cg_mount_table_lock);
1,104✔
1697

1698
        /* Free global variables filled by previous cgroup_init() */
1699
        cgroup_free_cg_mount_table();
1,104✔
1700

1701
        ret = cgroup_populate_controllers(controllers);
1,104✔
1702
        if (ret)
1,104✔
1703
                goto unlock_exit;
×
1704

1705
        ret = cgroup_populate_mount_points(controllers);
1,104✔
1706
        if (ret)
1,104✔
1707
                goto unlock_exit;
4✔
1708

1709
        cgroup_initialized = 1;
1,100✔
1710

1711
unlock_exit:
1,104✔
1712
        for (i = 0; controllers[i]; i++) {
16,560✔
1713
                free(controllers[i]);
15,456✔
1714
                controllers[i] = NULL;
15,456✔
1715
        }
1716

1717
        pthread_rwlock_unlock(&cg_mount_table_lock);
1,104✔
1718

1719
        return ret;
1,104✔
1720
}
1721

1722
static int cg_test_mounted_fs(void)
846✔
1723
{
1724
        char mntent_buff[4 * FILENAME_MAX];
1725
        struct mntent *temp_ent = NULL;
846✔
1726
        struct mntent *ent = NULL;
846✔
1727
        FILE *proc_mount = NULL;
846✔
1728
        int ret = 1;
846✔
1729

1730
        proc_mount = fopen("/proc/self/mounts", "re");
846✔
1731
        if (proc_mount == NULL)
846✔
1732
                return 0;
×
1733

1734
        temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
846✔
1735
        if (!temp_ent) {
846✔
1736
                /* We just fail at the moment. */
1737
                fclose(proc_mount);
×
1738
                return 0;
×
1739
        }
1740

1741
        ent = getmntent_r(proc_mount, temp_ent, mntent_buff, sizeof(mntent_buff));
846✔
1742
        if (!ent) {
846✔
1743
                ret = 0;
×
1744
                goto done;
×
1745
        }
1746

1747
        while (strcmp(ent->mnt_type, "cgroup") != 0 &&
10,827✔
1748
               strcmp(ent->mnt_type, "cgroup2") != 0) {
10,827✔
1749
                ent = getmntent_r(proc_mount, temp_ent, mntent_buff, sizeof(mntent_buff));
9,981✔
1750
                if (ent == NULL) {
9,981✔
1751
                        ret = 0;
×
1752
                        goto done;
×
1753
                }
1754
        }
1755
done:
846✔
1756
        fclose(proc_mount);
846✔
1757
        free(temp_ent);
846✔
1758

1759
        return ret;
846✔
1760
}
1761

1762
static inline pid_t cg_gettid(void)
2✔
1763
{
1764
        return syscall(__NR_gettid);
2✔
1765
}
1766

1767
static char *cg_concat_path(const char *pref, const char *suf, char *path)
27,379✔
1768
{
1769
        if ((suf[strlen(suf)-1] == '/') || ((strlen(suf) == 0) && (pref[strlen(pref)-1] == '/')))
27,379✔
1770
                snprintf(path, FILENAME_MAX, "%s%s", pref, suf+((suf[0] == '/') ? 1 : 0));
120✔
1771
        else
1772
                snprintf(path, FILENAME_MAX, "%s%s/", pref, suf+((suf[0] == '/') ? 1 : 0));
27,259✔
1773

1774
        path[FILENAME_MAX-1] = '\0';
27,379✔
1775

1776
        return path;
27,379✔
1777
}
1778

1779
/* Call with cg_mount_table_lock taken */
1780
/* path value have to have size at least FILENAME_MAX */
1781
char *cg_build_path_locked(const char *name, char *path, const char *type)
28,058✔
1782
{
1783
        char *tmp_systemd_default_cgrp, *_path = NULL;
28,058✔
1784
        /*
1785
         * len is the allocation size for path, that stores:
1786
         * cg_mount_table[i].mount.path + '/' + cg_namespace_table[i] + '/'
1787
         */
1788
        int i, ret, len = (FILENAME_MAX * 2) + 2;
28,058✔
1789

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

1802
#ifdef WITH_SYSTEMD
1803
        /*
1804
         * If the user specifies the name as /<cgroup-name>, they are
1805
         * effectively overriding the systemd_default_cgroup but if the name
1806
         * is "/", the cgroup root path is systemd_default_cgroup
1807
         */
1808
        if (strlen(systemd_default_cgroup) && name && name[0] == '/' && name[1] != '\0')
15,058✔
1809
                tmp_systemd_default_cgrp[0] = '\0';
1810
        else
1811
                snprintf(tmp_systemd_default_cgrp, len, "%s/", systemd_default_cgroup);
15,058✔
1812

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

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

1840
                strncpy(path, _path, FILENAME_MAX - 1);
125✔
1841
                path[FILENAME_MAX - 1] = '\0';
125✔
1842

1843
                if (name) {
125✔
1844
                        char *tmp;
1845

1846
                        tmp = strdup(path);
125✔
1847
                        if (tmp == NULL) {
125✔
1848
                                path = NULL;
×
1849
                                goto out;
×
1850
                        }
1851

1852
                        cg_concat_path(tmp, name, path);
125✔
1853
                        free(tmp);
125✔
1854
                }
1855
                goto out;
125✔
1856
        }
1857

1858
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
91,608✔
1859
                /* Two ways to successfully move forward here:
1860
                 * 1. The "type" controller matches the name of a mounted
1861
                 *    controller
1862
                 * 2. The "type" controller requested is "cgroup" and there's
1863
                 *    a "real" controller mounted as cgroup v2
1864
                 */
1865
                if ((type && strcmp(cg_mount_table[i].name, type) == 0) ||
91,606✔
1866
                    (type && strcmp(type, CGRP_FILE_PREFIX) == 0 &&
64,039✔
1867
                     cg_mount_table[i].version == CGROUP_V2)) {
364✔
1868

1869
                        if (cg_namespace_table[i])
27,931✔
1870
                                ret = snprintf(_path, len, "%s/%s%s/", cg_mount_table[i].mount.path,
4✔
1871
                                               tmp_systemd_default_cgrp, cg_namespace_table[i]);
1872
                        else
1873
                                ret = snprintf(_path, len, "%s/%s", cg_mount_table[i].mount.path,
27,927✔
1874
                                               tmp_systemd_default_cgrp);
1875

1876
                        if (ret >= FILENAME_MAX)
27,931✔
1877
                                cgroup_dbg("filename too long: %s", _path);
×
1878

1879
                        strncpy(path, _path, FILENAME_MAX - 1);
27,931✔
1880
                        path[FILENAME_MAX - 1] = '\0';
27,931✔
1881

1882
                        if (name) {
27,931✔
1883
                                char *tmp;
1884

1885
                                tmp = strdup(path);
27,254✔
1886
                                if (tmp == NULL)
27,254✔
1887
                                        break;
×
1888

1889
                                cg_concat_path(tmp, name, path);
27,254✔
1890
                                free(tmp);
27,254✔
1891
                        }
1892
                        goto out;
27,931✔
1893
                }
1894
        }
1895
        path = NULL;
2✔
1896

1897
out:
28,058✔
1898
        if (_path)
28,058✔
1899
                free(_path);
28,058✔
1900

1901
        if (tmp_systemd_default_cgrp)
28,058✔
1902
                free(tmp_systemd_default_cgrp);
28,058✔
1903

1904
        return path;
28,058✔
1905
}
1906

1907
char *cg_build_path(const char *name, char *path, const char *type)
1,638✔
1908
{
1909
        pthread_rwlock_rdlock(&cg_mount_table_lock);
1,638✔
1910
        path = cg_build_path_locked(name, path, type);
1,638✔
1911
        pthread_rwlock_unlock(&cg_mount_table_lock);
1,638✔
1912

1913
        return path;
1,638✔
1914
}
1915

1916
static int cgroup_get_cg_type(const char * const path, char * const type,
555✔
1917
                              size_t type_sz, bool is_tid)
1918
{
1919
        char cg_type_path[FILENAME_MAX];
1920
        char cg_type[CGV2_CONTROLLERS_LL_MAX];
1921
        int len, err = 0;
555✔
1922
        FILE *fp = NULL;
555✔
1923

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

1938
        if (fgets(cg_type, CGV2_CONTROLLERS_LL_MAX, fp) == NULL) {
429✔
1939
                cgroup_warn("failed to read file %s: %s\n", cg_type_path, strerror(errno));
×
1940
                err = ECGOTHER;
×
1941
                goto out;
×
1942
        }
1943

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

1970
out:
555✔
1971
        if (fp)
555✔
1972
                fclose(fp);
429✔
1973

1974
        return err;
555✔
1975
}
1976

1977
int cgroup_build_tasks_procs_path(char * const path, size_t path_sz, const char * const cg_name,
559✔
1978
                                  const char * const ctrl_name)
1979
{
1980
        enum cg_version_t version;
1981
        char cg_type[CGV2_CONTROLLERS_LL_MAX];
1982
        int err = ECGOTHER;
559✔
1983

1984
        if (!cg_build_path(cg_name, path, ctrl_name))
559✔
1985
                goto error;
1✔
1986

1987
        err = cgroup_get_controller_version(ctrl_name, &version);
558✔
1988
        if (err)
558✔
1989
                goto error;
×
1990

1991
        switch (version) {
558✔
1992
        case CGROUP_V1:
2✔
1993
                strncat(path, "tasks", path_sz - strlen(path));
2✔
1994
                err = 0;
2✔
1995
                break;
2✔
1996
        case CGROUP_V2:
555✔
1997
                err = cgroup_get_cg_type(path, cg_type, sizeof(cg_type), 0);
555✔
1998
                if (err)
555✔
1999
                        goto error;
×
2000

2001
                strncat(path, cg_type, path_sz - strlen(path));
555✔
2002
                break;
555✔
2003
        default:
1✔
2004
                err = ECGOTHER;
1✔
2005
                break;
1✔
2006
        }
2007

2008
error:
559✔
2009
        if (err)
559✔
2010
                path[0] = '\0';
2✔
2011

2012
        cgroup_dbg("cgroup build procs path: %s\n", path);
559✔
2013

2014
        return err;
559✔
2015
}
2016

2017
STATIC int cgroupv2_controller_enabled(const char * const cg_name, const char * const ctrl_name)
82✔
2018
{
2019
        char path[FILENAME_MAX] = {0};
82✔
2020
        char *parent = NULL, *dname;
82✔
2021
        enum cg_version_t version;
2022
        bool enabled;
2023
        int error;
2024

2025
        error = cgroup_get_controller_version(ctrl_name, &version);
82✔
2026
        if (error)
82✔
2027
                return error;
×
2028

2029
        if (version != CGROUP_V2)
82✔
2030
                return 0;
1✔
2031

2032
        if (ctrl_name == NULL)
81✔
2033
                /* cgroup v2 supports cgroups with no controllers. */
2034
                return 0;
6✔
2035

2036
        if (strncmp(cg_name, "/", strlen(cg_name)) == 0)
75✔
2037
                /*
2038
                 * The root cgroup has been requested.  All version 2
2039
                 * controllers are enabled on the root cgroup.
2040
                 */
2041
                return 0;
1✔
2042

2043
        if (!cg_build_path(cg_name, path, ctrl_name))
74✔
2044
                goto err;
×
2045

2046
        parent = strdup(path);
74✔
2047
        if (!parent) {
74✔
2048
                error = ECGOTHER;
×
2049
                goto err;
×
2050
        }
2051

2052
        dname = dirname(parent);
74✔
2053

2054
        error = cgroupv2_get_subtree_control(dname, ctrl_name, &enabled);
74✔
2055
        if (error)
74✔
2056
                goto err;
2✔
2057

2058
        if (enabled)
72✔
2059
                error = 0;
72✔
2060
err:
×
2061
        if (parent)
74✔
2062
                free(parent);
74✔
2063

2064
        return error;
74✔
2065
}
2066

2067
static int __cgroup_attach_task_pid(char *path, pid_t tid)
94✔
2068
{
2069
        FILE *tasks = NULL;
94✔
2070
        int ret = 0;
94✔
2071

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

2107
static int cgroup_build_tid_path(const char * const ctrl_name, char *path)
×
2108
{
2109
        char cg_type[CGV2_CONTROLLERS_LL_MAX];
2110
        enum cg_version_t version;
2111
        size_t len;
2112
        int ret;
2113

2114
        ret = cgroup_get_controller_version(ctrl_name, &version);
×
2115
        if (ret)
×
2116
                return ret;
×
2117

2118
        if (version == CGROUP_V2) {
×
2119

2120
                len = strlen(path) - 12;
×
2121
                if (strncmp(path + len, "cgroup.procs", 12) != 0) {
×
2122
                        ret = ECGOTHER;
×
2123
                        return ret;
×
2124
                }
2125

2126
                /* right trim cgroup.procs file name in the path */
2127
                path[len] = '\0';
×
2128

2129
                ret = cgroup_get_cg_type(path, cg_type, sizeof(cg_type), 1);
×
2130
                if (ret)
×
2131
                        return ret;
×
2132

2133
                strncat(path, cg_type, FILENAME_MAX - (len + 1));
×
2134
                path[FILENAME_MAX - 1] = '\0';
×
2135
        }
2136

2137
        if (version != CGROUP_V1)
×
2138
                return ret;
×
2139

2140
        /* replace tasks with cgroup.procs file name in the path */
2141
        len = strlen(path) - 5;
×
2142
        path[len] = '\0';
×
2143

2144
        strncat(path, "cgroup.procs", FILENAME_MAX - (len + 1));
×
2145
        path[FILENAME_MAX - 1] = '\0';
×
2146

2147
        return ret;
×
2148
}
2149

2150
static int cgroup_attach_task_tid(struct cgroup *cgrp, pid_t tid, bool move_tids)
58✔
2151
{
2152
        char path[FILENAME_MAX] = {0};
58✔
2153
        char *controller_name = NULL;
58✔
2154
        int empty_cgrp = 0;
58✔
2155
        int i, ret = 0;
58✔
2156

2157
        if (!cgroup_initialized) {
58✔
2158
                cgroup_warn("libcgroup is not initialized\n");
×
2159
                return ECGROUPNOTINITIALIZED;
×
2160
        }
2161

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

2171
                        if (move_tids) {
18✔
2172
                                ret = cgroup_build_tid_path(controller_name, path);
×
2173
                                if (ret)
×
2174
                                        return ret;
×
2175
                        }
2176

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

2193
                if (cgrp->index == 0)
56✔
2194
                        /* Valid empty cgroup v2 with no controllers added. */
2195
                        empty_cgrp = 1;
6✔
2196

2197
                for (i = 0, controller_name = NULL;
56✔
2198
                     empty_cgrp > 0 || i < cgrp->index;
128✔
2199
                     i++, empty_cgrp--) {
72✔
2200

2201
                        if (cgrp->controller[i])
76✔
2202
                                controller_name = cgrp->controller[i]->name;
70✔
2203

2204
                        ret = cgroupv2_controller_enabled(cgrp->name, controller_name);
76✔
2205
                        if (ret)
76✔
2206
                                return ret;
×
2207

2208
                        ret = cgroup_build_tasks_procs_path(path, sizeof(path), cgrp->name,
76✔
2209
                                                            controller_name);
2210
                        if (ret)
76✔
2211
                                return ret;
×
2212

2213
                        if (move_tids) {
76✔
2214
                                ret = cgroup_build_tid_path(controller_name, path);
×
2215
                                if (ret)
×
2216
                                        return ret;
×
2217
                        }
2218

2219
                        ret = __cgroup_attach_task_pid(path, tid);
76✔
2220
                        if (ret)
76✔
2221
                                return ret;
4✔
2222
                }
2223
        }
2224
        return 0;
54✔
2225
}
2226

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

2241
/**
2242
 * cgroup_attach_task is used to attach the current thread to a cgroup.
2243
 * struct cgroup *cgroup: The cgroup to assign the current thread to.
2244
 *
2245
 * See cg_attach_task_pid for return values.
2246
 */
2247
int cgroup_attach_task(struct cgroup *cgroup)
2✔
2248
{
2249
        pid_t tid = cg_gettid();
2✔
2250

2251
        return cgroup_attach_task_tid(cgroup, tid, 0);
2✔
2252
}
2253

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

2268
/**
2269
 * cg_mkdir_p, emulate the mkdir -p command (recursively creating paths)
2270
 * @path: path to create
2271
 */
2272
int cg_mkdir_p(const char *path)
488✔
2273
{
2274
        char *real_path = NULL;
488✔
2275
        char *tmp_path = NULL;
488✔
2276
        struct stat st;
2277
        int ret = 0;
488✔
2278
        int i = 0;
488✔
2279
        char pos;
2280

2281
        real_path = strdup(path);
488✔
2282
        if (!real_path) {
488✔
2283
                last_errno = errno;
×
2284
                return ECGOTHER;
×
2285
        }
2286

2287
        do {
2288
                while (real_path[i] != '\0' && real_path[i] == '/')
4,486✔
2289
                        i++;
2,310✔
2290

2291
                if (real_path[i] == '\0')
2,176✔
2292
                        break; /* The path ends with '/', ignore it. */
243✔
2293

2294
                while (real_path[i] != '\0' && real_path[i] != '/')
12,770✔
2295
                        i++;
10,837✔
2296

2297
                pos = real_path[i];
1,933✔
2298
                real_path[i] = '\0';                /* Temporarily overwrite "/" */
1,933✔
2299

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

2327
done:
245✔
2328
        free(real_path);
488✔
2329

2330
        return ret;
488✔
2331
}
2332

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

2343
        if (!cg_test_mounted_fs())
488✔
2344
                return ECGROUPNOTMOUNTED;
×
2345

2346
        error = cg_mkdir_p(path);
488✔
2347
        return error;
488✔
2348
}
2349

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

2363
        if (!cg_test_mounted_fs())
358✔
2364
                return ECGROUPNOTMOUNTED;
×
2365

2366
        ctl_file = open(path, O_RDWR | O_CLOEXEC);
358✔
2367

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

2381
                        path_dir_end = strrchr(path, '/');
×
2382
                        if (path_dir_end == NULL)
×
2383
                                return ECGROUPVALUENOTEXIST;
×
UNCOV
2384
                        path_dir_end = '\0';
×
2385

2386
                        /* task_path contain: $path/tasks */
2387
                        tasks_path = (char *)malloc(strlen(path) + 6 + 1);
×
2388
                        if (tasks_path == NULL) {
×
2389
                                last_errno = errno;
×
UNCOV
2390
                                return ECGOTHER;
×
2391
                        }
2392
                        strcpy(tasks_path, path);
×
UNCOV
2393
                        strcat(tasks_path, "/tasks");
×
2394

2395
                        /* Test tasks file for read flag */
2396
                        control_file = fopen(tasks_path, "re");
×
2397
                        if (!control_file) {
×
2398
                                if (errno == ENOENT) {
×
2399
                                        free(tasks_path);
×
UNCOV
2400
                                        return ECGROUPSUBSYSNOTMOUNTED;
×
2401
                                }
2402
                        } else {
UNCOV
2403
                                fclose(control_file);
×
2404
                        }
2405

2406
                        free(tasks_path);
×
UNCOV
2407
                        return ECGROUPNOTALLOWED;
×
2408
                }
UNCOV
2409
                return ECGROUPVALUENOTEXIST;
×
2410
        }
2411

2412
        /*
2413
         * Split the multiline value into lines.  One line is a special
2414
         * case of multiline value.
2415
         */
2416
        str_val = strdup(val);
358✔
2417
        if (str_val == NULL) {
358✔
2418
                last_errno = errno;
×
2419
                close(ctl_file);
×
UNCOV
2420
                return ECGOTHER;
×
2421
        }
2422

2423
        str_val_start = str_val;
358✔
2424
        pos = str_val;
358✔
2425

2426
        do {
2427
                str_val = pos;
370✔
2428
                pos = strchr(str_val, '\n');
370✔
2429

2430
                if (pos) {
370✔
2431
                        *pos = '\0';
12✔
2432
                        ++pos;
12✔
2433
                }
2434

2435
                len = strlen(str_val);
370✔
2436
                if (len > 0) {
370✔
2437
                        if (write(ctl_file, str_val, len) == -1) {
358✔
2438
                                last_errno = errno;
15✔
2439
                                free(str_val_start);
15✔
2440
                                close(ctl_file);
15✔
2441
                                return ECGOTHER;
15✔
2442
                        }
2443
                } else
2444
                        cgroup_warn("skipping empty line for %s\n", path);
12✔
2445
        } while (pos);
355✔
2446

2447
        if (close(ctl_file)) {
343✔
2448
                last_errno = errno;
×
2449
                free(str_val_start);
×
UNCOV
2450
                return ECGOTHER;
×
2451
        }
2452

2453
        free(str_val_start);
343✔
2454
        return 0;
343✔
2455
}
2456

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

2473
        for (j = 0; j < controller->index; j++) {
745✔
2474
                cv = controller->values[j];
294✔
2475

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

2492
                /* We don't support, writing multiline settings */
2493
                if (strcspn(cv->value, "\n")  < (strlen(cv->value) - 1))
294✔
UNCOV
2494
                        continue;
×
2495

2496
                ret = asprintf(&path, "%s%s", base, cv->name);
294✔
2497
                if (ret < 0) {
294✔
2498
                        last_errno = errno;
×
2499
                        error = ECGOTHER;
×
UNCOV
2500
                        goto err;
×
2501
                }
2502

2503
                /* skip read-only settings */
2504
                ret = (stat(path, &path_stat));
294✔
2505
                if (ret < 0) {
294✔
2506
                        last_errno = errno;
10✔
2507
                        error = ECGROUPVALUENOTEXIST;
10✔
2508
                        goto err;
10✔
2509
                }
2510

2511
                /* 0200 == S_IWUSR */
2512
                if (!(path_stat.st_mode & 0200)) {
284✔
2513
                        free(path);
×
2514
                        path = NULL;
×
UNCOV
2515
                        continue;
×
2516
                }
2517

2518
                cgroup_dbg("setting %s to \"%s\", pathlen %d\n", path, cv->value, ret);
284✔
2519

2520
                error = cg_set_control_value(path, cv->value);
284✔
2521
                free(path);
284✔
2522
                path = NULL;
284✔
2523

2524
                if (error) {
284✔
2525
                        /* Ignore the errors on deprecated settings */
2526
                        if (last_errno == EOPNOTSUPP) {
14✔
2527
                                error = 0;
×
UNCOV
2528
                                continue;
×
2529
                        }
2530
                        goto err;
14✔
2531
                }
2532
                cv->dirty = false;
270✔
2533
        }
2534

2535
err:
451✔
2536
        /*
2537
         * As currently written, path should always be null as we are\
2538
         * exiting this function, but let's check just in case, and free it
2539
         * if it's non-null
2540
         */
2541
        if (path)
475✔
2542
                free(path);
10✔
2543

2544
        return error;
475✔
2545
}
2546

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

2565
        if (!path || !ctrl_name || !enabled)
1,155✔
UNCOV
2566
                return ECGOTHER;
×
2567

2568
        *enabled = false;
1,155✔
2569

2570
        switch (file_enum) {
1,155✔
2571
        case 0: /* cgroup.subtree_control */
544✔
2572
                filename = CGV2_SUBTREE_CTRL_FILE;
544✔
2573
                break;
544✔
2574
        case 1: /* cgroup.controllers */
611✔
2575
                filename = CGV2_CONTROLLERS_FILE;
611✔
2576
                break;
611✔
2577
        default:
×
UNCOV
2578
                return ECGINVAL;
×
2579
        }
2580

2581
        path_copy = (char *)malloc(FILENAME_MAX);
1,155✔
2582
        if (!path_copy) {
1,155✔
2583
                error = ECGOTHER;
×
UNCOV
2584
                goto out;
×
2585
        }
2586

2587
        ret = snprintf(path_copy, FILENAME_MAX, "%s/%s", path, filename);
1,155✔
2588
        if (ret < 0) {
1,155✔
2589
                error = ECGOTHER;
×
UNCOV
2590
                goto out;
×
2591
        }
2592

2593
        fp = fopen(path_copy, "re");
1,155✔
2594
        if (!fp) {
1,155✔
2595
                cgroup_warn("fopen failed\n");
×
2596
                last_errno = errno;
×
2597
                error = ECGOTHER;
×
UNCOV
2598
                goto out;
×
2599
        }
2600

2601
        ret_c = fgets(buffer, sizeof(buffer), fp);
1,155✔
2602
        if (ret_c == NULL)
1,155✔
2603
                /* The subtree control file is empty */
2604
                goto out;
50✔
2605

2606
        /* Remove the trailing newline */
2607
        ret_c[strlen(ret_c) - 1] = '\0';
1,105✔
2608

2609
        /*
2610
         * Split the enabled controllers by " " and evaluate if the
2611
         * requested controller is enabled.
2612
         */
2613
        token = strtok_r(buffer, " ", &saveptr);
1,105✔
2614
        do {
2615
                if (strncmp(ctrl_name, token, FILENAME_MAX) == 0) {
3,388✔
2616
                        error = 0;
800✔
2617
                        *enabled = true;
800✔
2618
                        break;
800✔
2619
                }
2620
        } while ((token = strtok_r(NULL, " ", &saveptr)));
2,588✔
2621

2622
out:
305✔
2623
        if (path_copy)
1,155✔
2624
                free(path_copy);
1,155✔
2625
        if (fp)
1,155✔
2626
                fclose(fp);
1,155✔
2627

2628
        return error;
1,155✔
2629
}
2630

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

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

2669
        if (!path || !ctrl_name)
74✔
UNCOV
2670
                return ECGOTHER;
×
2671

2672
        value = (char *)malloc(FILENAME_MAX);
74✔
2673
        if (!value)
74✔
UNCOV
2674
                goto out;
×
2675

2676
        path_copy = (char *)malloc(FILENAME_MAX);
74✔
2677
        if (!path_copy)
74✔
UNCOV
2678
                goto out;
×
2679

2680
        ret = snprintf(path_copy, FILENAME_MAX, "%s/%s", path, CGV2_SUBTREE_CTRL_FILE);
74✔
2681
        if (ret < 0)
74✔
UNCOV
2682
                goto out;
×
2683

2684
        if (enable)
74✔
2685
                ret = snprintf(value, FILENAME_MAX, "+%s", ctrl_name);
73✔
2686
        else
2687
                ret = snprintf(value, FILENAME_MAX, "-%s", ctrl_name);
1✔
2688
        if (ret < 0)
74✔
UNCOV
2689
                goto out;
×
2690

2691
        error = cg_set_control_value(path_copy, value);
74✔
2692
        if (error)
74✔
2693
                goto out;
1✔
2694

2695
out:
73✔
2696
        if (value)
74✔
2697
                free(value);
74✔
2698
        if (path_copy)
74✔
2699
                free(path_copy);
74✔
2700
        return error;
74✔
2701
}
2702

2703
/*
2704
 * Test if the controller is enabled in the root_cgrp.subtree_control file
2705
 * and enable the controller, if not enabled.
2706
 */
2707
static int test_and_set_ctrl_mnt_path(const char * const mount_path, const char * const ctrl_name)
219✔
2708
{
2709
        bool enabled;
2710
        int ret;
2711

2712
        /* return if the controller is enabled */
2713
        ret = cgroupv2_get_subtree_control(mount_path, ctrl_name, &enabled);
219✔
2714
        if (ret == ECGOTHER)
219✔
UNCOV
2715
                return ret;
×
2716

2717
        if (enabled == true)
219✔
2718
                return 0;
209✔
2719

2720
        /* check if the controller is available is controllers file */
2721
        ret = cgroupv2_get_controllers(mount_path, ctrl_name, &enabled);
10✔
2722
        if (ret == ECGOTHER || ret == ECGROUPNOTMOUNTED)
10✔
UNCOV
2723
                return ret;
×
2724

2725
        /* enable the controller in the subtree_control file */
2726
        ret = cgroupv2_subtree_control(mount_path, ctrl_name, 1);
10✔
2727
        if (ret)
10✔
UNCOV
2728
                return ret;
×
2729

2730
        return 0;
10✔
2731
}
2732

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

2747
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
567✔
2748
                if (strncmp(cg_mount_table[i].name, ctrl_name,
567✔
2749
                            sizeof(cg_mount_table[i].name)) == 0) {
2750
                        found_mount = true;
219✔
2751
                        break;
219✔
2752
                }
2753
        }
2754

2755
        if (!found_mount)
219✔
UNCOV
2756
                return ECGROUPSUBSYSNOTMOUNTED;
×
2757

2758
        path_copy = strdup(path);
219✔
2759
        if (!path_copy)
219✔
UNCOV
2760
                return ECGOTHER;
×
2761

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

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

2786
        tmp_path = strtok_r(&path[mount_len], "/", &stok_buff);
219✔
2787
        do {
2788
                if (tmp_path) {
245✔
2789
                        strcat(path_copy, "/");
94✔
2790
                        strcat(path_copy, tmp_path);
94✔
2791
                }
2792

2793
                error = cg_create_control_group(path_copy);
245✔
2794
                if (error)
245✔
UNCOV
2795
                        goto out;
×
2796

2797
                error = cgroupv2_get_subtree_control(path_copy, ctrl_name, &controller_enabled);
245✔
2798
                if (controller_enabled)
245✔
2799
                        continue;
183✔
2800
                if (error != ECGROUPNOTMOUNTED)
62✔
UNCOV
2801
                        goto out;
×
2802

2803
                error = cgroupv2_subtree_control(path_copy, ctrl_name, enable);
62✔
2804
                if (error)
62✔
2805
                        goto out;
1✔
2806
        } while ((tmp_path = strtok_r(NULL, "/", &stok_buff)));
244✔
2807

2808
out:
218✔
2809
        free(path_copy);
219✔
2810
        return error;
219✔
2811
}
2812

2813
/**
2814
 * cgroup_modify_cgroup modifies the cgroup control files.
2815
 * struct cgroup *cgrp: The name will be the cgroup to be modified.
2816
 * The values will be the values to be modified, those not mentioned in the
2817
 * structure will not be modified.
2818
 *
2819
 * The uids cannot be modified yet.
2820
 *
2821
 * returns 0 on success.
2822
 */
2823

2824
int cgroup_modify_cgroup(struct cgroup *cgrp)
275✔
2825
{
2826
        char base[FILENAME_MAX];
2827
        int error = 0;
275✔
2828
        int i;
2829

2830
        if (!cgroup_initialized)
275✔
UNCOV
2831
                return ECGROUPNOTINITIALIZED;
×
2832

2833
        if (!cgrp)
275✔
UNCOV
2834
                return ECGROUPNOTALLOWED;
×
2835

2836
        for (i = 0; i < cgrp->index; i++) {
529✔
2837
                if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name)) {
254✔
2838
                        cgroup_warn("subsystem %s is not mounted\n", cgrp->controller[i]->name);
×
UNCOV
2839
                        return ECGROUPSUBSYSNOTMOUNTED;
×
2840
                }
2841
        }
2842

2843
        for (i = 0; i < cgrp->index; i++) {
525✔
2844
                if (!cg_build_path(cgrp->name, base, cgrp->controller[i]->name))
254✔
UNCOV
2845
                        continue;
×
2846

2847
                error = cgroup_set_values_recursive(base, cgrp->controller[i], true);
254✔
2848
                if (error)
254✔
2849
                        goto err;
4✔
2850
        }
2851
err:
271✔
2852
        return error;
275✔
2853
}
2854

2855
int cgroup_copy_controller_values(struct cgroup_controller * const dst,
404✔
2856
                                  const struct cgroup_controller * const src)
2857
{
2858
        int i, ret = 0;
404✔
2859

2860
        if (!dst || !src)
404✔
UNCOV
2861
                return ECGFAIL;
×
2862

2863
        strncpy(dst->name, src->name, CONTROL_NAMELEN_MAX);
404✔
2864
        for (i = 0; i < src->index; i++, dst->index++) {
810✔
2865
                struct control_value *src_val = src->values[i];
406✔
2866
                struct control_value *dst_val;
2867

2868
                dst->values[i] = calloc(1, sizeof(struct control_value));
406✔
2869
                if (!dst->values[i]) {
406✔
2870
                        last_errno = errno;
×
2871
                        ret = ECGOTHER;
×
UNCOV
2872
                        goto err;
×
2873
                }
2874

2875
                dst_val = dst->values[i];
406✔
2876
                strncpy(dst_val->value, src_val->value, CG_CONTROL_VALUE_MAX);
406✔
2877
                strncpy(dst_val->name, src_val->name, FILENAME_MAX);
406✔
2878

2879
                if (src_val->multiline_value) {
406✔
2880
                        dst_val->multiline_value = strdup(src_val->multiline_value);
×
2881
                        if (!dst_val->multiline_value) {
×
2882
                                last_errno = errno;
×
2883
                                ret = ECGOTHER;
×
UNCOV
2884
                                goto err;
×
2885
                        }
2886
                } else {
2887
                        dst_val->multiline_value = NULL;
406✔
2888
                }
2889

2890
                if (src_val->prev_name) {
406✔
2891
                        dst_val->prev_name = strdup(src_val->prev_name);
×
2892
                        if (!dst_val->prev_name) {
×
2893
                                last_errno = errno;
×
2894
                                ret = ECGOTHER;
×
UNCOV
2895
                                goto err;
×
2896
                        }
2897
                } else {
2898
                        dst_val->prev_name = NULL;
406✔
2899
                }
2900
                /*
2901
                 * set dirty flag unconditionally, as we overwrite
2902
                 * destination controller values.
2903
                 */
2904
                dst_val->dirty = true;
406✔
2905
        }
2906

2907
        return ret;
404✔
2908

2909
err:
×
2910
        dst->index = 0;
×
2911
        for (i = 0; i < src->index; i++) {
×
2912
                if (dst->values[i]) {
×
2913
                        if (dst->values[i]->multiline_value)
×
UNCOV
2914
                                free(dst->values[i]->multiline_value);
×
2915

2916
                        if (dst->values[i]->prev_name)
×
UNCOV
2917
                                free(dst->values[i]->prev_name);
×
2918

UNCOV
2919
                        free(dst->values[i]);
×
2920
                }
2921
        }
2922

UNCOV
2923
        return ret;
×
2924
}
2925

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

2937
        if (!dst || !src)
281✔
UNCOV
2938
                return ECGROUPNOTEXIST;
×
2939

2940
        /* Should we just use the restrict keyword instead? */
2941
        if (dst == src)
281✔
UNCOV
2942
                return ECGFAIL;
×
2943

2944
        cgroup_free_controllers(dst);
281✔
2945

2946
        for (i = 0; i < src->index; i++, dst->index++) {
541✔
2947
                struct cgroup_controller *src_ctlr = src->controller[i];
260✔
2948
                struct cgroup_controller *dst_ctlr;
2949

2950
                dst->controller[i] = calloc(1, sizeof(struct cgroup_controller));
260✔
2951
                if (!dst->controller[i]) {
260✔
2952
                        last_errno = errno;
×
2953
                        ret = ECGOTHER;
×
UNCOV
2954
                        goto err;
×
2955
                }
2956

2957
                dst_ctlr = dst->controller[i];
260✔
2958
                ret = cgroup_copy_controller_values(dst_ctlr, src_ctlr);
260✔
2959
                if (ret)
260✔
UNCOV
2960
                        goto err;
×
2961
        }
2962
err:
281✔
2963
        return ret;
281✔
2964
}
2965

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

2978
        tasks_path = (char *)malloc(FILENAME_MAX);
1✔
2979
        if (tasks_path == NULL)
1✔
UNCOV
2980
                return ECGOTHER;
×
2981

2982
        ret = snprintf(tasks_path, FILENAME_MAX, "%s/tasks", cg_path);
1✔
2983
        if (ret < 0 || ret >= FILENAME_MAX) {
1✔
2984
                last_errno = errno;
×
2985
                error = ECGOTHER;
×
UNCOV
2986
                goto err;
×
2987
        }
2988

2989
        error = cg_chown(tasks_path, uid, gid);
1✔
2990
        if (!error && fperm != NO_PERMS)
1✔
2991
                error = cg_chmod_path(tasks_path, fperm, 1);
1✔
2992

2993
        if (error) {
1✔
2994
                last_errno = errno;
×
UNCOV
2995
                error = ECGOTHER;
×
2996
        }
2997

2998
err:
1✔
2999
        if (tasks_path)
1✔
3000
                free(tasks_path);
1✔
3001

3002
        return error;
1✔
3003
}
3004

3005
static int _cgroup_create_cgroup(const struct cgroup * const cgrp,
244✔
3006
                                 const struct cgroup_controller * const controller,
3007
                                 int ignore_ownership)
3008
{
3009
        enum cg_version_t version = CGROUP_UNK;
244✔
3010
        char *fts_path[2];
3011
        char *base = NULL;
244✔
3012
        char *path = NULL;
244✔
3013
        int error;
3014

3015
        fts_path[0] = (char *)malloc(FILENAME_MAX);
244✔
3016
        if (!fts_path[0]) {
244✔
3017
                last_errno = errno;
×
UNCOV
3018
                return ECGOTHER;
×
3019
        }
3020
        fts_path[1] = NULL;
244✔
3021
        path = fts_path[0];
244✔
3022

3023
        if (controller) {
244✔
3024
                if (!cg_build_path(cgrp->name, path, controller->name)) {
221✔
3025
                        error = ECGOTHER;
×
UNCOV
3026
                        goto err;
×
3027
                }
3028

3029
                error = cgroup_get_controller_version(controller->name, &version);
221✔
3030
                if (error)
221✔
UNCOV
3031
                        goto err;
×
3032

3033
                if (version == CGROUP_V2) {
221✔
3034
                        char *parent, *dname;
3035

3036
                        parent = strdup(path);
219✔
3037
                        if (!parent) {
219✔
3038
                                error = ECGOTHER;
×
UNCOV
3039
                                goto err;
×
3040
                        }
3041

3042
                        dname = dirname(parent);
219✔
3043

3044
                        error = cgroupv2_subtree_control_recursive(dname, controller->name, true);
219✔
3045
                        free(parent);
219✔
3046
                        if (error)
219✔
3047
                                goto err;
1✔
3048
                }
3049
        } else {
3050
                if (!cg_build_path(cgrp->name, path, NULL)) {
23✔
3051
                        error = ECGOTHER;
×
UNCOV
3052
                        goto err;
×
3053
                }
3054
        }
3055

3056
        error = cg_create_control_group(path);
243✔
3057
        if (error)
243✔
UNCOV
3058
                goto err;
×
3059

3060
        base = strdup(path);
243✔
3061

3062
        if (!base) {
243✔
3063
                last_errno = errno;
×
3064
                error = ECGOTHER;
×
UNCOV
3065
                goto err;
×
3066
        }
3067

3068
        if (!ignore_ownership) {
243✔
3069
                cgroup_dbg("Changing ownership of %s\n", fts_path[0]);
233✔
3070
                error = cg_chown_recursive(fts_path, cgrp->control_uid, cgrp->control_gid);
233✔
3071
                if (!error)
233✔
3072
                        error = cg_chmod_recursive_controller(fts_path[0],
233✔
3073
                                                              cgrp->control_dperm,
233✔
3074
                                                              cgrp->control_dperm != NO_PERMS,
233✔
3075
                                                              cgrp->control_fperm,
233✔
3076
                                                              cgrp->control_fperm != NO_PERMS,
233✔
3077
                                                              1, cgroup_ignored_tasks_files);
3078
        }
3079

3080
        if (error)
243✔
UNCOV
3081
                goto err;
×
3082

3083
        if (controller) {
243✔
3084
                error = cgroup_set_values_recursive(base, controller, false);
220✔
3085
                if (error)
220✔
3086
                        goto err;
20✔
3087
        }
3088

3089
        if (!ignore_ownership && version == CGROUP_V1) {
223✔
3090
                error = cgroup_chown_chmod_tasks(base, cgrp->tasks_uid, cgrp->tasks_gid,
×
3091
                                                 cgrp->task_fperm);
×
3092
                if (error)
×
UNCOV
3093
                        goto err;
×
3094
        }
3095
err:
223✔
3096
        if (path)
244✔
3097
                free(path);
244✔
3098
        if (base)
244✔
3099
                free(base);
243✔
3100

3101
        return error;
244✔
3102
}
3103

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

3118
        if (!cgroup_initialized)
211✔
UNCOV
3119
                return ECGROUPNOTINITIALIZED;
×
3120

3121
        if (!cgrp)
211✔
UNCOV
3122
                return ECGROUPNOTALLOWED;
×
3123

3124
        for (i = 0; i < cgrp->index; i++) {
432✔
3125
                if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name))
221✔
UNCOV
3126
                        return ECGROUPSUBSYSNOTMOUNTED;
×
3127
        }
3128

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

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

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

3165
        return 0;
190✔
3166
}
3167

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

3188
        dir = strdup(cgrp->name);
184✔
3189
        if (!dir) {
184✔
3190
                last_errno = errno;
×
UNCOV
3191
                return ECGOTHER;
×
3192
        }
3193
        cgroup_dbg("group name is %s\n", dir);
184✔
3194

3195
        pdir = dirname(dir);
184✔
3196
        cgroup_dbg("parent's group name is %s\n", pdir);
184✔
3197

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

3211
        return ret;
184✔
3212
}
3213

3214
/*
3215
 * Checks if the cgroup's controller shares the mount point with any other
3216
 * controller in cg_mount_table.
3217
 * Returns 1 if shared or 0.
3218
 */
3219
static int is_cgrp_ctrl_shared_mnt(char *controller)
189✔
3220
{
3221
        int ret = 0;
189✔
3222
        int i;
3223

3224
        if (!controller)
189✔
3225
                return ret;
22✔
3226

3227
        pthread_rwlock_rdlock(&cg_mount_table_lock);
167✔
3228

3229
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
379✔
3230

3231
                if (strncmp(cg_mount_table[i].name, controller, CONTROL_NAMELEN_MAX) == 0 &&
379✔
3232
                    cg_mount_table[i].shared_mnt) {
167✔
3233
                        ret = 1;
167✔
3234
                        break;
167✔
3235
                }
3236
        }
3237

3238
        pthread_rwlock_unlock(&cg_mount_table_lock);
167✔
3239

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

3265
        *parent = NULL;
189✔
3266

3267
        pthread_rwlock_rdlock(&cg_mount_table_lock);
189✔
3268
        if (!cg_build_path_locked(cgrp->name, child_path, controller)) {
189✔
3269
                pthread_rwlock_unlock(&cg_mount_table_lock);
×
UNCOV
3270
                return ECGFAIL;
×
3271
        }
3272
        pthread_rwlock_unlock(&cg_mount_table_lock);
189✔
3273

3274
        cgroup_dbg("path is %s\n", child_path);
189✔
3275

3276
        if (asprintf(&parent_path, "%s/..", child_path) < 0)
189✔
UNCOV
3277
                return ECGFAIL;
×
3278

3279
        cgroup_dbg("parent's name is %s\n", parent_path);
189✔
3280

3281
        if (stat(child_path, &stat_child) < 0) {
189✔
3282
                if (is_cgrp_ctrl_shared_mnt(controller)) {
5✔
3283
                        last_errno = errno;
5✔
3284
                        ret = ECGROUPNOTEXIST;
5✔
3285
                        goto free_parent;
5✔
3286
                }
3287
                last_errno = errno;
×
3288
                ret = ECGOTHER;
×
UNCOV
3289
                goto free_parent;
×
3290
        }
3291

3292
        if (stat(parent_path, &stat_parent) < 0) {
184✔
3293
                last_errno = errno;
×
3294
                ret = ECGOTHER;
×
UNCOV
3295
                goto free_parent;
×
3296
        }
3297

3298
        /* Is the specified "name" a mount point? */
3299
        if (stat_parent.st_dev != stat_child.st_dev) {
184✔
3300
                *parent = NULL;
×
3301
                ret = 0;
×
UNCOV
3302
                cgroup_dbg("Parent is on different device\n");
×
3303
        } else {
3304
                ret = cgroup_get_parent_name(cgrp, parent);
184✔
3305
        }
3306

3307
free_parent:
189✔
3308
        free(parent_path);
189✔
3309
        return ret;
189✔
3310
}
3311

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

3327
        if (!cgroup_initialized)
×
UNCOV
3328
                return ECGROUPNOTINITIALIZED;
×
3329

3330
        ret = cgroup_get_parent_name(cgrp, &parent);
×
3331
        if (ret)
×
UNCOV
3332
                return ret;
×
3333

UNCOV
3334
        if (parent == NULL) {
×
3335
                /*
3336
                 * The group to create is root group!
3337
                 * TODO: find better error code?
3338
                 */
UNCOV
3339
                return ECGFAIL;
×
3340
        }
3341

3342
        cgroup_dbg("parent is %s\n", parent);
×
3343
        parent_cgrp = cgroup_new_cgroup(parent);
×
3344
        if (!parent_cgrp) {
×
3345
                ret = ECGFAIL;
×
UNCOV
3346
                goto err_nomem;
×
3347
        }
3348

3349
        if (cgroup_get_cgroup(parent_cgrp)) {
×
3350
                ret = ECGFAIL;
×
UNCOV
3351
                goto err_parent;
×
3352
        }
3353

3354
        cgroup_dbg("got parent group for %s\n", parent_cgrp->name);
×
3355
        ret = cgroup_copy_cgroup(cgrp, parent_cgrp);
×
3356
        if (ret)
×
UNCOV
3357
                goto err_parent;
×
3358

3359
        cgroup_dbg("copied parent group %s to %s\n", parent_cgrp->name, cgrp->name);
×
UNCOV
3360
        ret = cgroup_create_cgroup(cgrp, ignore_ownership);
×
3361

3362
err_parent:
×
3363
        cgroup_free(&parent_cgrp);
×
3364
err_nomem:
×
3365
        free(parent);
×
UNCOV
3366
        return ret;
×
3367
}
3368

3369
/**
3370
 * Move all processes from one task file to another.
3371
 * @param input_tasks Pre-opened file to read tasks from.
3372
 * @param output_tasks Pre-opened file to write tasks to.
3373
 * @return 0 on success, >0 on error.
3374
 */
3375
static int cg_move_task_files(FILE *input_tasks, FILE *output_tasks)
216✔
3376
{
3377
        int ret = 0;
216✔
3378
        int tids;
3379

3380
        while (!feof(input_tasks)) {
236✔
3381
                ret = fscanf(input_tasks, "%d", &tids);
236✔
3382
                if (ret == EOF || ret == 0) {
236✔
3383
                        ret = 0;
216✔
3384
                        break;
216✔
3385
                }
3386
                if (ret < 0)
20✔
UNCOV
3387
                        break;
×
3388

3389
                ret = fprintf(output_tasks, "%d", tids);
20✔
3390
                if (ret < 0 && errno != ESRCH)
20✔
UNCOV
3391
                        break;
×
3392

3393
                /* Flush the file, we need only one process per write() call. */
3394
                ret = fflush(output_tasks);
20✔
3395
                if (ret < 0) {
20✔
3396
                        if (errno == ESRCH)
×
UNCOV
3397
                                ret = 0;
×
3398
                        else
UNCOV
3399
                                break;
×
3400
                }
3401
        }
3402

3403
        if (ret < 0) {
216✔
3404
                last_errno = errno;
×
UNCOV
3405
                return ECGOTHER;
×
3406
        }
3407
        return 0;
216✔
3408
}
3409

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

3434
        cgroup_dbg("Removing group %s:%s\n", controller, cgrp_name);
216✔
3435

3436
        if (!(flags & CGFLAG_DELETE_EMPTY_ONLY)) {
216✔
3437
                /* Open tasks file of the group to delete. */
3438
                ret = cgroup_build_tasks_procs_path(path, sizeof(path), cgrp_name, controller);
216✔
3439
                if (ret != 0)
216✔
UNCOV
3440
                        return ECGROUPSUBSYSNOTMOUNTED;
×
3441

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

3462
                if (ret != 0 && !(flags & CGFLAG_DELETE_IGNORE_MIGRATION))
216✔
UNCOV
3463
                        return ret;
×
3464
        }
3465

3466
        /* Remove the group. */
3467
        if (!cg_build_path(cgrp_name, path, controller))
216✔
UNCOV
3468
                return ECGROUPSUBSYSNOTMOUNTED;
×
3469

3470
        ret = rmdir(path);
216✔
3471
        if (ret == 0 || errno == ENOENT)
216✔
3472
                return 0;
216✔
3473

3474
        if ((flags & CGFLAG_DELETE_EMPTY_ONLY) && (errno == EBUSY))
×
UNCOV
3475
                return ECGNONEMPTY;
×
3476

3477
        cgroup_warn("cannot remove directory %s: %s\n", path, strerror(errno));
×
UNCOV
3478
        last_errno = errno;
×
3479

UNCOV
3480
        return ECGOTHER;
×
3481
}
3482

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

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

3505
        ret = cgroup_walk_tree_begin(controller, cgrp_name, 0, &handle, &info, &level);
21✔
3506
        if (ret == 0)
21✔
3507
                ret = cgroup_walk_tree_set_flags(&handle, CGROUP_WALK_TYPE_POST_DIR);
21✔
3508

3509
        if (ret != 0) {
21✔
3510
                cgroup_walk_tree_end(&handle);
×
UNCOV
3511
                return ret;
×
3512
        }
3513

3514
        group_len = strlen(info.full_path);
21✔
3515

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

3519
        while (ret == 0) {
2,386✔
3520
                if (info.type == CGROUP_FILE_TYPE_DIR && info.depth > 0) {
2,365✔
3521
                        snprintf(child_name, sizeof(child_name), "%s/%s", cgrp_name,
32✔
3522
                                 info.full_path + group_len);
32✔
3523

3524
                        ret = cg_delete_cgrp_controller(child_name, controller, target_tasks,
32✔
3525
                                                          flags);
3526
                        if (ret != 0)
32✔
UNCOV
3527
                                break;
×
3528
                }
3529

3530
                ret = cgroup_walk_tree_next(0, &handle, &info, level);
2,365✔
3531
        }
3532
        if (ret == ECGEOF) {
21✔
3533
                /* Iteration finished successfully, remove the root group. */
3534
                ret = 0;
21✔
3535
                if (delete_root)
21✔
3536
                        ret = cg_delete_cgrp_controller(cgrp_name, controller, target_tasks, flags);
21✔
3537
        }
3538

3539
        cgroup_walk_tree_end(&handle);
21✔
3540

3541
        return ret;
21✔
3542
}
3543

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

3554
        return cgroup_delete_cgroup_ext(cgrp, flags);
27✔
3555
}
3556

3557
int cgroup_delete_cgroup_ext(struct cgroup *cgrp, int flags)
188✔
3558
{
3559
        int first_error = 0, first_errno = 0;
188✔
3560
        int cgrp_del_on_shared_mnt = 0;
188✔
3561
        char parent_path[FILENAME_MAX];
3562
        char *controller_name = NULL;
188✔
3563
        FILE *parent_tasks = NULL;
188✔
3564
        char *parent_name = NULL;
188✔
3565
        int delete_group = 1;
188✔
3566
        int empty_cgrp = 0;
188✔
3567
        int i, ret;
3568

3569
        if (!cgroup_initialized)
188✔
UNCOV
3570
                return ECGROUPNOTINITIALIZED;
×
3571

3572
        if (!cgrp)
188✔
UNCOV
3573
                return ECGROUPNOTALLOWED;
×
3574

3575
        if ((flags & CGFLAG_DELETE_RECURSIVE)
188✔
3576
            && (flags & CGFLAG_DELETE_EMPTY_ONLY))
25✔
UNCOV
3577
                return ECGINVAL;
×
3578

3579
        if (cgrp->index == 0)
188✔
3580
                /* Valid empty cgroup v2 with not controllers added. */
3581
                empty_cgrp = 1;
22✔
3582

3583
        for (i = 0; i < cgrp->index; i++) {
355✔
3584
                if (!cgroup_test_subsys_mounted(cgrp->controller[i]->name))
167✔
UNCOV
3585
                        return ECGROUPSUBSYSNOTMOUNTED;
×
3586
        }
3587

3588
        /*
3589
         * Remove the group from all controllers and in the case of cgroup
3590
         * with no controllers, perform all actions of a single controller.
3591
         */
3592
        for (i = 0; empty_cgrp > 0 || i < cgrp->index; i++, empty_cgrp--) {
377✔
3593

3594
                ret = 0;
189✔
3595
                controller_name = NULL;
189✔
3596

3597
                if (cgrp->controller[i])
189✔
3598
                        controller_name = cgrp->controller[i]->name;
167✔
3599

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

3624
                        if (is_cgrp_ctrl_shared_mnt(controller_name))
184✔
3625
                                cgrp_del_on_shared_mnt = 1;
162✔
3626

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

3648
                if (parent_name) {
184✔
3649
                        /* Tasks need to be moved, pre-open target tasks file */
3650
                        ret = cgroup_build_tasks_procs_path(parent_path, sizeof(parent_path),
184✔
3651
                                                            parent_name, controller_name);
3652
                        if (ret != 0) {
184✔
3653
                                if (first_error == 0)
×
3654
                                        first_error = ECGFAIL;
×
3655
                                free(parent_name);
×
UNCOV
3656
                                continue;
×
3657
                        }
3658

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

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

3705
        /*
3706
         * Restore the last_errno to the first errno from
3707
         * cg_delete_cgroup_controller[_ext].
3708
         */
3709
        if (first_errno != 0)
188✔
3710
                last_errno = first_errno;
4✔
3711

3712
        return first_error;
188✔
3713
}
3714

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

3725
        if (!cg_build_path_locked(cgrp, path, subsys))
3,402✔
UNCOV
3726
                return ECGFAIL;
×
3727

3728
        strncat(path, file, sizeof(path) - strlen(path));
3,402✔
3729
        ctrl_file = fopen(path, "re");
3,402✔
3730
        if (!ctrl_file)
3,402✔
3731
                return ECGROUPVALUENOTEXIST;
10✔
3732

3733
        *value = calloc(CG_CONTROL_VALUE_MAX, 1);
3,392✔
3734
        if (!*value) {
3,392✔
3735
                fclose(ctrl_file);
×
3736
                last_errno = errno;
×
UNCOV
3737
                return ECGOTHER;
×
3738
        }
3739

3740
        /* Using %as crashes when we try to read from files like memory.stat */
3741
        ret = fread(*value, 1, CG_CONTROL_VALUE_MAX-1, ctrl_file);
3,392✔
3742
        if (ret < 0) {
3,392✔
3743
                free(*value);
×
UNCOV
3744
                *value = NULL;
×
3745
        } else {
3746
                /* Remove trailing \n */
3747
                if (ret > 0 && (*value)[ret-1] == '\n')
3,392✔
3748
                        (*value)[ret-1] = '\0';
3,169✔
3749
        }
3750

3751
        fclose(ctrl_file);
3,392✔
3752

3753
        return 0;
3,392✔
3754
}
3755

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

3773
        d_name = strdup(ctrl_dir->d_name);
21,516✔
3774

3775
        if (!d_name) {
21,516✔
3776
                error = ECGOTHER;
×
3777
                cgroup_err("strdup failed to allocate memory: %s\n", strerror(errno));
×
UNCOV
3778
                goto fill_error;
×
3779
        }
3780

3781
        if (!strcmp(d_name, ".") || !strcmp(d_name, "..")) {
21,516✔
3782
                error = ECGINVAL;
×
UNCOV
3783
                goto fill_error;
×
3784
        }
3785

3786
        /*
3787
         * This part really needs to be optimized out. Probably use some
3788
         * sort of a flag, but this is fine for now.
3789
         */
3790
        cg_build_path_locked(cgrp->name, path, cg_mount_table[cg_index].name);
21,516✔
3791
        strncat(path, d_name, sizeof(path) - strlen(path));
21,516✔
3792

3793
        error = stat(path, &stat_buffer);
21,516✔
3794
        if (error) {
21,516✔
3795
                error = ECGFAIL;
×
UNCOV
3796
                goto fill_error;
×
3797
        }
3798

3799
        /*
3800
         * We have already stored the tasks_uid & tasks_gid. This check is
3801
         * to avoid the overwriting of the values stored in
3802
         * control_uid & cotrol_gid. tasks file will have the uid and gid of
3803
         * the user who is capable of putting a task to this cgroup.
3804
         * control_uid and control_gid is meant for the users who are capable
3805
         * of managing the cgroup shares.
3806
         *
3807
         * The strstr() function will return the pointer to the
3808
         * beginning of the sub string "/tasks".
3809
         */
3810
        tmp_len = strlen(path) - strlen("/tasks");
21,516✔
3811

3812
        /* tmp_path would be pointing to the last six characters */
3813
        tmp_path = (char *)path + tmp_len;
21,516✔
3814

3815
        /*
3816
         * Checking to see, if this is actually a 'tasks' file We need to
3817
         * compare the last 6 bytes
3818
         */
3819
        if (strcmp(tmp_path, "/tasks")) {
21,516✔
3820
                cgrp->control_uid = stat_buffer.st_uid;
21,507✔
3821
                cgrp->control_gid = stat_buffer.st_gid;
21,507✔
3822
        }
3823

3824
        ctrl_name = strtok_r(d_name, ".", &buffer);
21,516✔
3825
        if (!ctrl_name) {
21,516✔
3826
                error = ECGFAIL;
×
UNCOV
3827
                goto fill_error;
×
3828
        }
3829

3830
        ctrl_file = strtok_r(NULL, ".", &buffer);
21,516✔
3831
        if (!ctrl_file) {
21,516✔
3832
                error = ECGINVAL;
9✔
3833
                goto fill_error;
9✔
3834
        }
3835

3836
        if (strcmp(ctrl_name, cg_mount_table[cg_index].name) == 0) {
21,507✔
3837
                error = cg_rd_ctrl_file(cg_mount_table[cg_index].name, cgrp->name,
3,402✔
3838
                                        ctrl_dir->d_name, &ctrl_value);
3,402✔
3839
                if (error || !ctrl_value)
3,402✔
3840
                        goto fill_error;
10✔
3841

3842
                if (cgroup_add_value_string(cgc, ctrl_dir->d_name, ctrl_value)) {
3,392✔
3843
                        error = ECGFAIL;
×
UNCOV
3844
                        goto fill_error;
×
3845
                }
3846
        }
3847
fill_error:
21,497✔
3848
        if (ctrl_value)
21,516✔
3849
                free(ctrl_value);
3,392✔
3850
        if (d_name)
21,516✔
3851
                free(d_name);
21,516✔
3852

3853
        return error;
21,516✔
3854
}
3855

3856
/*
3857
 * cgroup_get_cgroup reads the cgroup data from the filesystem.
3858
 * struct cgroup has the name of the group to be populated
3859
 *
3860
 * return 0 on success.
3861
 */
3862
int cgroup_get_cgroup(struct cgroup *cgrp)
73✔
3863
{
3864
        char cgrp_ctrl_path[FILENAME_MAX];
3865
        struct dirent *ctrl_dir = NULL;
73✔
3866
        char mnt_path[FILENAME_MAX];
3867
        int initial_controller_cnt;
3868
        char *control_path = NULL;
73✔
3869
        int controller_cnt = 0;
73✔
3870
        DIR *dir = NULL;
73✔
3871
        int error;
3872
        int i, j;
3873
        int ret;
3874

3875
        if (!cgroup_initialized) {
73✔
3876
                /* ECGROUPNOTINITIALIZED */
UNCOV
3877
                return ECGROUPNOTINITIALIZED;
×
3878
        }
3879

3880
        if (!cgrp) {
73✔
3881
                /* ECGROUPNOTALLOWED */
UNCOV
3882
                return ECGROUPNOTALLOWED;
×
3883
        }
3884

3885
        initial_controller_cnt = cgrp->index;
73✔
3886

3887
        pthread_rwlock_rdlock(&cg_mount_table_lock);
73✔
3888
        for (i = 0; i < CG_CONTROLLER_MAX && cg_mount_table[i].name[0] != '\0'; i++) {
723✔
3889
                struct cgroup_controller *cgc;
3890
                struct stat stat_buffer;
3891
                int mnt_path_len;
3892

3893
                if (initial_controller_cnt > 0) {
651✔
3894
                        bool skip_this_controller = true;
54✔
3895

3896
                        /*
3897
                         * The user has specified a list of controllers they are interested
3898
                         * in.  Only operate on the specified controllers
3899
                         */
3900
                        for (j = 0; j < cgrp->index; j++) {
198✔
3901
                                if (strncmp(cg_mount_table[i].name, cgrp->controller[j]->name,
144✔
3902
                                            CONTROL_NAMELEN_MAX) == 0)
3903
                                        skip_this_controller = false;
16✔
3904
                        }
3905

3906
                        if (skip_this_controller)
54✔
3907
                                continue;
314✔
3908
                }
3909

3910
                if (!cg_build_path_locked(NULL, mnt_path, cg_mount_table[i].name))
613✔
UNCOV
3911
                        continue;
×
3912

3913
                mnt_path_len = strlen(mnt_path);
613✔
3914
                strncat(mnt_path, cgrp->name, FILENAME_MAX - mnt_path_len - 1);
613✔
3915
                mnt_path[sizeof(mnt_path) - 1] = '\0';
613✔
3916

3917
                if (access(mnt_path, F_OK))
613✔
3918
                        continue;
2✔
3919

3920
                if (!cg_build_path_locked(cgrp->name, cgrp_ctrl_path, cg_mount_table[i].name)) {
611✔
3921
                        /* This fails when the cgroup does not exist for that controller. */
UNCOV
3922
                        continue;
×
3923
                }
3924

3925
                /* Get the uid and gid information. */
3926
                if (cg_mount_table[i].version == CGROUP_V1) {
611✔
3927
                        ret = asprintf(&control_path, "%s/tasks", cgrp_ctrl_path);
10✔
3928

3929
                        if (ret < 0) {
10✔
3930
                                last_errno = errno;
×
UNCOV
3931
                                error = ECGOTHER;
×
3932
                                goto unlock_error;
1✔
3933
                        }
3934

3935
                        if (stat(control_path, &stat_buffer)) {
10✔
3936
                                last_errno = errno;
1✔
3937
                                free(control_path);
1✔
3938
                                error = ECGOTHER;
1✔
3939
                                goto unlock_error;
1✔
3940
                        }
3941

3942
                        cgrp->tasks_uid = stat_buffer.st_uid;
9✔
3943
                        cgrp->tasks_gid = stat_buffer.st_gid;
9✔
3944

3945
                        free(control_path);
9✔
3946
                } else { /* cgroup v2 */
3947
                        bool enabled;
3948

3949
                        error = cgroupv2_get_controllers(cgrp_ctrl_path, cg_mount_table[i].name,
601✔
3950
                                                         &enabled);
3951
                        if (error == ECGROUPNOTMOUNTED) {
601✔
3952
                                /*
3953
                                 * This controller isn't enabled.  Only hide it from the
3954
                                 * user if they've chosen to view all enabled controllers.
3955
                                 *
3956
                                 * If they've specified the controllers they're interested in
3957
                                 * and we've made it this far, then they are explicitly
3958
                                 * interested in this controller and we should not remove it.
3959
                                 */
3960
                                if (initial_controller_cnt == 0) {
278✔
3961
                                        controller_cnt++;
274✔
3962
                                        continue;
274✔
3963
                                }
3964
                        } else if (error) {
323✔
UNCOV
3965
                                goto unlock_error;
×
3966
                        }
3967
                }
3968

3969
                if (initial_controller_cnt)
336✔
3970
                        cgc = cgroup_get_controller(cgrp, cg_mount_table[i].name);
16✔
3971
                else
3972
                        cgc = cgroup_add_controller(cgrp, cg_mount_table[i].name);
320✔
3973
                if (!cgc) {
336✔
3974
                        error = ECGINVAL;
×
UNCOV
3975
                        goto unlock_error;
×
3976
                }
3977

3978
                dir = opendir(cgrp_ctrl_path);
336✔
3979
                if (!dir) {
336✔
3980
                        last_errno = errno;
×
3981
                        error = ECGOTHER;
×
UNCOV
3982
                        goto unlock_error;
×
3983
                }
3984

3985
                controller_cnt++;
336✔
3986

3987
                while ((ctrl_dir = readdir(dir)) != NULL) {
20,325✔
3988
                        /* Skip over non regular files */
3989
                        if (ctrl_dir->d_type != DT_REG)
19,989✔
3990
                                continue;
917✔
3991

3992
                        error = cgroup_fill_cgc(ctrl_dir, cgrp, cgc, i);
19,072✔
3993
                        for (j = 0; j < cgc->index; j++)
111,692✔
3994
                                cgc->values[j]->dirty = false;
92,620✔
3995

3996
                        if (error == ECGFAIL) {
19,072✔
3997
                                closedir(dir);
×
UNCOV
3998
                                goto unlock_error;
×
3999
                        }
4000
                }
4001
                closedir(dir);
336✔
4002

4003
                if (!strcmp(cgc->name, "memory")) {
336✔
4004
                        /*
4005
                         * Make sure that memory.limit_in_bytes is placed before
4006
                         * memory.memsw.limit_in_bytes in the list of values
4007
                         */
4008
                        int memsw_limit = -1;
69✔
4009
                        int mem_limit = -1;
69✔
4010

4011
                        for (j = 0; j < cgc->index; j++) {
1,472✔
4012
                                if (!strcmp(cgc->values[j]->name, "memory.memsw.limit_in_bytes"))
1,403✔
4013
                                        memsw_limit = j;
2✔
4014
                                else if (!strcmp(cgc->values[j]->name, "memory.limit_in_bytes"))
1,401✔
4015
                                        mem_limit = j;
2✔
4016
                        }
4017

4018
                        if (memsw_limit >= 0 && memsw_limit < mem_limit) {
69✔
4019
                                struct control_value *val = cgc->values[memsw_limit];
2✔
4020

4021
                                cgc->values[memsw_limit] = cgc->values[mem_limit];
2✔
4022
                                cgc->values[mem_limit] = val;
2✔
4023
                        }
4024
                }
4025
        }
4026

4027
        /*
4028
         * Check if the group really exists or not.  The cgrp->index controller count can't
4029
         * be used in this case because cgroup v2 allows controllers to be enabled/disabled in
4030
         * the subtree_control file.  Rather, cgroup_get_cgroup() tracks the number of possible
4031
         * controllers in the controller_cnt variable and uses that to determine if the cgroup
4032
         * exists or not.
4033
         */
4034
        if (!controller_cnt) {
72✔
4035
                error = ECGROUPNOTEXIST;
×
UNCOV
4036
                goto unlock_error;
×
4037
        }
4038

4039
        pthread_rwlock_unlock(&cg_mount_table_lock);
72✔
4040

4041
        return 0;
72✔
4042

4043
unlock_error:
1✔
4044
        pthread_rwlock_unlock(&cg_mount_table_lock);
1✔
4045
        /*
4046
         * XX: Need to figure out how to cleanup? Cleanup just the stuff
4047
         * we added, or the whole structure.
4048
         */
4049
        cgroup_free_controllers(cgrp);
1✔
4050
        cgrp = NULL;
1✔
4051

4052
        return error;
1✔
4053
}
4054

4055
/**
4056
 * cg_prepare_cgroup Process the selected rule. Prepare the cgroup structure
4057
 * which can be used to add the task to destination cgroup.
4058
 *
4059
 *  returns 0 on success.
4060
 */
4061
static int cg_prepare_cgroup(struct cgroup *cgrp, pid_t pid, const char *dest,
47✔
4062
                             const char * const controllers[])
4063
{
4064
        struct cgroup_controller *cptr = NULL;
47✔
4065
        const char *controller = NULL;
47✔
4066
        int ret = 0, i;
47✔
4067

4068
        /* Fill in cgroup details.  */
4069
        cgroup_dbg("Will move pid %d to cgroup '%s'\n", pid, dest);
47✔
4070

4071
        strncpy(cgrp->name, dest, FILENAME_MAX);
47✔
4072
        cgrp->name[FILENAME_MAX-1] = '\0';
47✔
4073

4074
        /* Scan all the controllers */
4075
        for (i = 0; i < CG_CONTROLLER_MAX; i++) {
109✔
4076
                int j = 0;
109✔
4077

4078
                if (!controllers[i])
109✔
4079
                        return 0;
47✔
4080
                controller = controllers[i];
62✔
4081

4082
                /* If first string is "*" that means all the mounted controllers. */
4083
                if (strcmp(controller, "*") == 0) {
62✔
UNCOV
4084
                        pthread_rwlock_rdlock(&cg_mount_table_lock);
×
4085

4086
                        for (j = 0; j < CG_CONTROLLER_MAX &&
×
4087
                                cg_mount_table[j].name[0] != '\0'; j++) {
×
4088
                                cgroup_dbg("Adding controller %s\n", cg_mount_table[j].name);
×
4089
                                cptr = cgroup_add_controller(cgrp, cg_mount_table[j].name);
×
4090
                                if (!cptr) {
×
UNCOV
4091
                                        cgroup_warn("adding controller '%s' failed\n",
×
4092
                                                    cg_mount_table[j].name);
4093
                                        pthread_rwlock_unlock(&cg_mount_table_lock);
×
4094
                                        cgroup_free_controllers(cgrp);
×
UNCOV
4095
                                        return ECGROUPNOTALLOWED;
×
4096
                                }
4097
                        }
4098
                        pthread_rwlock_unlock(&cg_mount_table_lock);
×
UNCOV
4099
                        return ret;
×
4100
                }
4101

4102
                /* It is individual controller names and not "*" */
4103
                cgroup_dbg("Adding controller %s\n", controller);
62✔
4104
                cptr = cgroup_add_controller(cgrp, controller);
62✔
4105
                if (!cptr) {
62✔
4106
                        cgroup_warn("adding controller '%s' failed\n", controller);
×
4107
                        cgroup_free_controllers(cgrp);
×
UNCOV
4108
                        return ECGROUPNOTALLOWED;
×
4109
                }
4110
        }
4111

UNCOV
4112
        return ret;
×
4113
}
4114

4115
/**
4116
 * Determines if the rule is a wildcard rule and if so, compares the wildcard
4117
 * rule against the new process.  If the new process matches the wildcard rule,
4118
 * then this function returns true. Otherwise it returns false.
4119
 *
4120
 *        @param rule_procname The procname field of the rule
4121
 *        @param procname The name of the new process
4122
 *        @return True if the procname matches the rule.  False otherwise
4123
 */
4124
STATIC bool cgroup_compare_wildcard_procname(const char * const rule_procname,
227✔
4125
                                             const char * const procname)
4126
{
4127
        size_t rule_strlen = strlen(rule_procname);
227✔
4128

4129
        if (rule_procname[rule_strlen - 1] != '*')
227✔
4130
                /* This rule does not end in a wildcard */
4131
                return false;
220✔
4132

4133
        /* Compare the two strings up to the asterisk */
4134
        if (strncmp(rule_procname, procname, rule_strlen - 1) != 0)
7✔
4135
                /* The strings did not match */
4136
                return false;
3✔
4137

4138
        /* All checks passed.  The wildcarded process matched this rule */
4139
        return true;
4✔
4140
}
4141

4142
static int cgroup_find_matching_destination(char *cgrp_list[], const char * const rule_dest,
14✔
4143
                                            int *matching_index)
4144
{
4145
        size_t rule_strlen = strlen(rule_dest);
14✔
4146
        int ret = -ENODATA;
14✔
4147
        int i;
4148

4149
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
34✔
4150
                if (cgrp_list[i] == NULL)
34✔
4151
                        break;
5✔
4152

4153
                if (rule_dest[rule_strlen - 1] == '/') {
29✔
4154
                        /*
4155
                         * Avoid a weird corner case where given a rule dest
4156
                         * like 'folder/', we _don't_ want to match 'folder1'
4157
                         */
4158
                        if (strlen(cgrp_list[i]) >= rule_strlen &&
7✔
4159
                            cgrp_list[i][rule_strlen - 1] != '/')
6✔
4160
                                continue;
3✔
4161

4162
                        /*
4163
                         * Strip off the '/' at the end of the rule, as
4164
                         * the destination from the cgrp_list will not
4165
                         * have a trailing '/'
4166
                         */
4167
                        rule_strlen--;
4✔
4168
                }
4169

4170
                if (strncmp(rule_dest, cgrp_list[i], rule_strlen) == 0) {
26✔
4171
                        *matching_index = i;
9✔
4172
                        ret = 0;
9✔
4173
                        break;
9✔
4174
                }
4175
        }
4176

4177
        return ret;
14✔
4178
}
4179

4180
static int cgroup_find_matching_controller(char * const *rule_controllers,
10✔
4181
                                           const char * const pid_controller, int *matching_index)
4182
{
4183
        int ret = -ENODATA;
10✔
4184
        int i;
4185

4186
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
11✔
4187
                if (rule_controllers[i] == NULL)
11✔
4188
                        break;
1✔
4189

4190
                if (strlen(rule_controllers[i]) != strlen(pid_controller))
10✔
4191
                        continue;
1✔
4192

4193
                if (strncmp(pid_controller, rule_controllers[i], strlen(pid_controller)) == 0) {
9✔
4194
                        *matching_index = i;
9✔
4195
                        ret = 0;
9✔
4196
                        break;
9✔
4197
                }
4198
        }
4199

4200
        return ret;
10✔
4201
}
4202

4203
static bool cgroup_is_rt_task(const pid_t pid)
14✔
4204
{
4205
        int sched_prio_min, sched_prio_max;
4206
        struct sched_param pid_param;
4207
        int ret;
4208

4209
        ret = sched_getparam(pid, &pid_param);
14✔
4210
        if (ret == -1) {
14✔
4211
                ret = ECGOTHER;
14✔
4212
                last_errno = errno;
14✔
4213
                return false;
14✔
4214
        }
4215

4216
        sched_prio_min = sched_get_priority_min(SCHED_RR);
×
UNCOV
4217
        sched_prio_max = sched_get_priority_max(SCHED_RR);
×
4218

4219
        if (pid_param.sched_priority >= sched_prio_min &&
×
4220
            pid_param.sched_priority <= sched_prio_max)
×
UNCOV
4221
                return true;
×
4222

UNCOV
4223
        return false;
×
4224
}
4225

4226
/**
4227
 * Evaluates if rule is an ignore rule and the pid/procname match this rule.
4228
 * If rule is an ignore rule and the pid/procname match this rule, then this
4229
 * function returns true.  Otherwise it returns false.
4230
 *
4231
 *        @param rule The rule being evaluated
4232
 *        @param pid PID of the process being compared
4233
 *        @param procname Process name of the process being compared
4234
 *        @return True if the rule is an ignore rule and this pid/procname
4235
 *                match the rule.  False otherwise
4236
 */
4237
STATIC bool cgroup_compare_ignore_rule(const struct cgroup_rule * const rule, pid_t pid,
234✔
4238
                                       const char * const procname)
4239
{
4240
        char *controller_list[MAX_MNT_ELEMENTS] = { '\0' };
234✔
4241
        char *cgrp_list[MAX_MNT_ELEMENTS] = { '\0' };
234✔
4242
        int rule_matching_controller_idx;
4243
        int cgrp_list_matching_idx = 0;
234✔
4244
        bool found_match = false;
234✔
4245
        char *token, *saveptr;
4246
        int ret, i;
4247

4248
        if (!rule->is_ignore)
234✔
4249
                /* Immediately return if the 'ignore' option is not set */
4250
                return false;
220✔
4251

4252
        /* If the rule is "ignore", move only non-rt tasks */
4253
        if (rule->is_ignore == CGRULE_OPT_IGNORE && cgroup_is_rt_task(pid) == true)
14✔
UNCOV
4254
                return false;
×
4255
        /* If the rule is "ignore_rt", move only non-rt tasks */
4256
        else if (rule->is_ignore == CGRULE_OPT_IGNORE_RT && cgroup_is_rt_task(pid) == false)
14✔
UNCOV
4257
                return false;
×
4258

4259
        /* If the rule is "ignore" and "ignore_rt", move all tasks */
4260

4261
        ret = cg_get_cgroups_from_proc_cgroups(pid, cgrp_list, controller_list,
14✔
4262
                                               MAX_MNT_ELEMENTS);
4263
        if (ret < 0)
14✔
UNCOV
4264
                goto out;
×
4265

4266
        if (strcmp(rule->destination, "*")) {
14✔
4267
                ret = cgroup_find_matching_destination(cgrp_list, rule->destination,
14✔
4268
                                                       &cgrp_list_matching_idx);
4269
                if (ret < 0)
14✔
4270
                        /* No cgroups matched */
4271
                        goto out;
5✔
4272
        }
4273

4274
        token = strtok_r(controller_list[cgrp_list_matching_idx], ",", &saveptr);
9✔
4275
        while (token != NULL) {
10✔
4276

4277
                ret = cgroup_find_matching_controller(rule->controllers, token,
10✔
4278
                                                      &rule_matching_controller_idx);
4279
                if (ret == 0)
10✔
4280
                        /* We found a matching controller */
4281
                        break;
9✔
4282

4283
                token = strtok_r(NULL, ",", &saveptr);
1✔
4284
        }
4285

4286
        if (!rule->procname) {
9✔
4287
                /*
4288
                 * The rule procname is empty, thus it's a wildcard and
4289
                 * all processes match.
4290
                 */
4291
                found_match = true;
5✔
4292
                goto out;
5✔
4293
        }
4294

4295
        if (!strcmp(rule->procname, procname)) {
4✔
4296
                found_match = true;
2✔
4297
                goto out;
2✔
4298
        }
4299

4300
        if (cgroup_compare_wildcard_procname(rule->procname, procname))
2✔
4301
                found_match = true;
1✔
4302

4303
out:
1✔
4304
        for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
252✔
4305
                if (controller_list[i])
238✔
4306
                        free(controller_list[i]);
36✔
4307
                if (cgrp_list[i])
238✔
4308
                        free(cgrp_list[i]);
36✔
4309
        }
4310

4311
        return found_match;
14✔
4312
}
4313

4314
static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(uid_t uid, gid_t gid,
219✔
4315
                                                             struct cgroup_rule *rule)
4316
{
4317
        /* Temporary user data */
4318
        struct passwd *usr = NULL;
219✔
4319

4320
        /* Temporary group data */
4321
        struct group *grp = NULL;
219✔
4322

4323
        /* Temporary string pointer */
4324
        char *sp = NULL;
219✔
4325

4326
        /* Loop variable */
4327
        int i = 0;
219✔
4328
        int loglevel;
4329
        bool match_found = false;
219✔
4330

4331
        loglevel = cgroup_get_loglevel();
219✔
4332

4333
        while (rule) {
219✔
4334
                /* Skip "%" which indicates continuation of previous rule. */
4335
                if (rule->username[0] == '%') {
219✔
4336
                        rule = rule->next;
×
UNCOV
4337
                        continue;
×
4338
                }
4339
                /* The wildcard rule always matches. */
4340
                if ((rule->uid == CGRULE_WILD) && (rule->gid == CGRULE_WILD))
219✔
4341
                        return rule;
219✔
4342

4343
                /* This is the simple case of the UID matching. */
4344
                if (rule->uid == uid)
×
UNCOV
4345
                        return rule;
×
4346

4347
                /* This is the simple case of the GID matching. */
4348
                if (rule->gid == gid)
×
UNCOV
4349
                        return rule;
×
4350

4351
                /* If this is a group rule, the UID might be a member. */
UNCOV
4352
                if (rule->username[0] == '@') {
×
4353
                        /* Get the group data. */
4354
                        sp = &(rule->username[1]);
×
4355
                        grp = getgrnam(sp);
×
4356
                        if (!grp) {
×
4357
                                rule = rule->next;
×
UNCOV
4358
                                continue;
×
4359
                        }
4360

4361
                        /* Get the data for UID. */
4362
                        usr = getpwuid(uid);
×
4363
                        if (!usr) {
×
4364
                                rule = rule->next;
×
UNCOV
4365
                                continue;
×
4366
                        }
4367

UNCOV
4368
                        cgroup_dbg("User name: %s UID: %d Group name: %s GID: %d\n",
×
4369
                                   usr->pw_name, uid, grp->gr_name, grp->gr_gid);
4370
                        if (grp->gr_mem[0])
×
UNCOV
4371
                                cgroup_dbg("Group member(s):\n");
×
4372

4373
                        /* If UID is a member of group, we matched. */
4374
                        for (i = 0; grp->gr_mem[i]; i++) {
×
4375
                                if (!(strcmp(usr->pw_name, grp->gr_mem[i])))
×
UNCOV
4376
                                        match_found = true;
×
4377

UNCOV
4378
                                if (match_found && loglevel < CGROUP_LOG_DEBUG)
×
4379
                                        /*
4380
                                         * Only continue to run through the loop if debugging is
4381
                                         * enabled so that we can see all of the group members
4382
                                         */
UNCOV
4383
                                        break;
×
4384

UNCOV
4385
                                cgroup_dbg("\t%s\n", grp->gr_mem[i]);
×
4386
                        }
4387

4388
                        if (match_found)
×
UNCOV
4389
                                return rule;
×
4390
                }
4391

4392
                /* If we haven't matched, try the next rule. */
UNCOV
4393
                rule = rule->next;
×
4394
        }
4395

4396
        /* If we get here, no rules matched. */
UNCOV
4397
        return NULL;
×
4398
}
4399

4400
/**
4401
 * Finds the first rule in the cached list that matches the given UID, GID
4402
 * or PROCESS NAME, and returns a pointer to that rule.
4403
 * This function uses rl_lock.
4404
 *
4405
 * This function may NOT be thread safe.
4406
 *        @param uid The UID to match
4407
 *        @param gid The GID to match
4408
 *        @param procname The PROCESS NAME to match
4409
 *        @return Pointer to the first matching rule, or NULL if no match
4410
 * TODO: Determine thread-safeness and fix if not safe.
4411
 */
4412
static struct cgroup_rule *cgroup_find_matching_rule(uid_t uid, gid_t gid, pid_t pid,
219✔
4413
                                                     const char *procname)
4414
{
4415
        /* Return value */
4416
        struct cgroup_rule *ret = rl.head;
219✔
4417
        char *base = NULL;
219✔
4418

4419
        pthread_rwlock_wrlock(&rl_lock);
219✔
4420
        while (ret) {
437✔
4421
                ret = cgroup_find_matching_rule_uid_gid(uid, gid, ret);
219✔
4422
                if (!ret)
219✔
UNCOV
4423
                        break;
×
4424
                if (cgroup_compare_ignore_rule(ret, pid, procname))
219✔
4425
                        /*
4426
                         * This pid matched a rule that instructs the
4427
                         * cgrules daemon to ignore this process.
4428
                         */
UNCOV
4429
                        break;
×
4430
                if (ret->is_ignore) {
219✔
4431
                        /*
4432
                         * The rule currently being examined is an ignore
4433
                         * rule, but it didn't match this pid. Move on to
4434
                         * the next rule
4435
                         */
4436
                        ret = ret->next;
×
UNCOV
4437
                        continue;
×
4438
                }
4439
                if (!procname)
219✔
4440
                        /* If procname is NULL, return a rule matching UID or GID. */
UNCOV
4441
                        break;
×
4442
                if (!ret->procname)
219✔
4443
                        /* If no process name in a rule, that means wildcard */
UNCOV
4444
                        break;
×
4445
                if (!strcmp(ret->procname, procname))
219✔
4446
                        break;
1✔
4447

4448
                base = cgroup_basename(procname);
218✔
4449
                if (!strcmp(ret->procname, base))
218✔
4450
                        /* Check a rule of basename. */
UNCOV
4451
                        break;
×
4452
                if (cgroup_compare_wildcard_procname(ret->procname, procname))
218✔
UNCOV
4453
                        break;
×
4454
                ret = ret->next;
218✔
4455
                free(base);
218✔
4456
                base = NULL;
218✔
4457
        }
4458
        pthread_rwlock_unlock(&rl_lock);
219✔
4459

4460
        if (base)
219✔
UNCOV
4461
                free(base);
×
4462

4463
        return ret;
219✔
4464
}
4465

4466
/*
4467
 * Procedure the existence of cgroup "prefix" is in subsystem
4468
 * controller_name return 0 on success
4469
 */
UNCOV
4470
int cgroup_exist_in_subsystem(char *controller_name, char *prefix)
×
4471
{
4472
        char path[FILENAME_MAX];
4473
        char *ret_path;
4474
        DIR *dir;
4475
        int ret;
4476

4477
        pthread_rwlock_rdlock(&cg_mount_table_lock);
×
4478
        ret_path = cg_build_path_locked(prefix, path, controller_name);
×
4479
        pthread_rwlock_unlock(&cg_mount_table_lock);
×
4480
        if (!ret_path) {
×
4481
                ret = 1;
×
UNCOV
4482
                goto end;
×
4483
        }
4484

4485
        dir = opendir(path);
×
UNCOV
4486
        if (dir == NULL) {
×
4487
                /* cgroup in wanted subsystem does not exist */
UNCOV
4488
                ret = 1;
×
4489
        } else {
4490
                /* cgroup in wanted subsystem exists */
4491
                ret = 0;
×
UNCOV
4492
                closedir(dir);
×
4493
        }
4494
end:
×
UNCOV
4495
        return ret;
×
4496
}
4497

4498
/*
4499
 * Auxiliary function return a pointer to the string which is copy of
4500
 * input string and end with the slash
4501
 */
UNCOV
4502
char *cgroup_copy_with_slash(char *input)
×
4503
{
UNCOV
4504
        int len = strlen(input);
×
4505
        char *output;
4506

4507
        /* If input does not end with '/', allocate one more space for it */
4508
        if ((input[len-1]) != '/')
×
UNCOV
4509
                len = len+1;
×
4510

4511
        output = (char *)malloc(sizeof(char)*(len+1));
×
4512
        if (output == NULL)
×
UNCOV
4513
                return NULL;
×
4514

4515
        strcpy(output, input);
×
4516
        output[len-1] = '/';
×
UNCOV
4517
        output[len] = '\0';
×
4518

UNCOV
4519
        return output;
×
4520
}
4521

4522
/* Add controller to a group if it is not exists create it */
UNCOV
4523
static int add_controller(struct cgroup **pcgrp, char *cgrp_name,
×
4524
                          char controller_name[FILENAME_MAX])
4525
{
4526
        struct cgroup_controller *controller = NULL;
×
4527
        struct cgroup *cgrp = pcgrp[0];
×
UNCOV
4528
        int ret = 0;
×
4529

UNCOV
4530
        if  (cgrp == NULL) {
×
4531
                /* It is the first controller the cgrp have to be created */
4532
                cgrp = cgroup_new_cgroup(cgrp_name);
×
4533
                if (cgrp == NULL) {
×
4534
                        ret = ECGFAIL;
×
UNCOV
4535
                        goto end;
×
4536
                }
UNCOV
4537
                pcgrp[0] = cgrp;
×
4538
        }
4539

4540
        controller = cgroup_add_controller(cgrp, controller_name);
×
4541
        if (controller == NULL) {
×
4542
                cgroup_free(&cgrp);
×
UNCOV
4543
                ret = ECGFAIL;
×
4544
        }
4545
end:
×
UNCOV
4546
        return ret;
×
4547
}
4548

4549
/*
4550
 * Create control group based given template if the group already don't exist
4551
 * dest is template name with substitute variables tmp is used cgrules rule.
4552
 */
UNCOV
4553
static int cgroup_create_template_group(char *orig_group_name, struct cgroup_rule *tmp, int flags)
×
4554
{
4555

4556
        struct cgroup *template_group = NULL;
×
4557
        char *template_name = NULL;        /* Name of the template.              */
×
UNCOV
4558
        char *group_name = NULL;        /* Name of the group based on         */
×
4559
                                        /* template variables are substituted.*/
4560
        char *template_position;        /* Denotes directory in template      */
4561
                                        /* path which is investigated.        */
4562
        char *group_position;                /* Denotes directory in cgroup path   */
4563
                                        /* which is investigated.             */
4564

UNCOV
4565
        int ret = 0;
×
4566
        int exist;
4567
        int i;
4568

4569
        /* Template name and group name have to have '/' sign at the end */
4570
        template_name = cgroup_copy_with_slash(tmp->destination);
×
4571
        if (template_name == NULL) {
×
4572
                ret = ECGOTHER;
×
4573
                last_errno = errno;
×
UNCOV
4574
                goto end;
×
4575
        }
4576
        group_name = cgroup_copy_with_slash(orig_group_name);
×
4577
        if (group_name == NULL) {
×
4578
                ret = ECGOTHER;
×
4579
                last_errno = errno;
×
4580
                free(template_name);
×
4581
                template_name = NULL;
×
UNCOV
4582
                goto end;
×
4583
        }
4584

4585
        /* Set start positions */
4586
        template_position = strchr(template_name, '/');
×
UNCOV
4587
        group_position = strchr(group_name, '/');
×
4588

4589
        /*
4590
         * Go recursively through whole path to template group and create
4591
         * given directory if it does not exist yet
4592
         */
UNCOV
4593
        while ((group_position != NULL) && (template_position != NULL)) {
×
4594
                /* Set new subpath */
4595
                group_position[0] = '\0';
×
4596
                template_position[0] = '\0';
×
UNCOV
4597
                template_group = NULL;
×
4598

4599
                /* Test for which controllers wanted group does not exist */
4600
                i = 0;
×
4601
                while (i < MAX_MNT_ELEMENTS && tmp->controllers[i] != NULL) {
×
UNCOV
4602
                        exist = cgroup_exist_in_subsystem(tmp->controllers[i], group_name);
×
4603

UNCOV
4604
                        if (exist != 0) {
×
4605
                                /* The cgroup does not exist */
UNCOV
4606
                                ret = add_controller(&template_group, group_name,
×
4607
                                                     tmp->controllers[i]);
4608
                                if  (ret != 0)
×
UNCOV
4609
                                        goto while_end;
×
4610
                        }
UNCOV
4611
                        i++;
×
4612
                }
4613

UNCOV
4614
                if (template_group != NULL) {
×
4615
                        /*  New group have to be created */
UNCOV
4616
                        if (strcmp(group_name, template_name) == 0) {
×
4617
                                /* The prefix cgroup without template */
UNCOV
4618
                                ret = cgroup_create_cgroup(template_group, 0);
×
4619
                        } else {
4620
                                /* Use template to create relevant cgroup */
UNCOV
4621
                                ret = cgroup_config_create_template_group(template_group,
×
4622
                                                                          template_name, flags);
4623
                        }
4624

4625
                        if (ret != 0) {
×
4626
                                cgroup_free(&template_group);
×
UNCOV
4627
                                goto while_end;
×
4628
                        }
UNCOV
4629
                        cgroup_dbg("Group %s created - based on template %s\n", group_name,
×
4630
                                   template_name);
4631

UNCOV
4632
                        cgroup_free(&template_group);
×
4633
                }
4634
                template_position[0] = '/';
×
4635
                group_position[0] = '/';
×
4636
                template_position = strchr(++template_position, '/');
×
UNCOV
4637
                group_position = strchr(++group_position, '/');
×
4638
        }
4639

4640
while_end:
×
4641
        if ((template_position != NULL) && (template_position[0] == '\0'))
×
4642
                template_position[0] = '/';
×
4643
        if ((group_position != NULL) && (group_position[0] == '\0'))
×
UNCOV
4644
                group_position[0] = '/';
×
4645

4646
end:
×
4647
        if (group_name != NULL)
×
4648
                free(group_name);
×
4649
        if (template_name != NULL)
×
UNCOV
4650
                free(template_name);
×
4651

UNCOV
4652
        return ret;
×
4653
}
4654

4655
int cgroup_change_cgroup_flags(uid_t uid, gid_t gid, const char *procname, pid_t pid, int flags)
219✔
4656
{
4657
        /* Temporary pointer to a rule */
4658
        struct cgroup_rule *tmp = NULL;
219✔
4659

4660
        /* Temporary variables for destination substitution */
4661
        char newdest[FILENAME_MAX];
4662
        struct passwd *user_info;
4663
        struct group *group_info;
4664
        int available;
4665
        int written;
4666
        int i, j;
4667

4668
        /* Return codes */
4669
        int ret = 0;
219✔
4670

4671
        /* We need to check this before doing anything else! */
4672
        if (!cgroup_initialized) {
219✔
4673
                cgroup_warn("libcgroup is not initialized\n");
×
4674
                ret = ECGROUPNOTINITIALIZED;
×
UNCOV
4675
                goto finished;
×
4676
        }
4677

4678
        /*
4679
         * User had asked to find the matching rule (if one exist) in the
4680
         * cached rules but the list might be empty due to the inactive
4681
         * cgrulesengd. Lets emulate its behaviour of caching the rules by
4682
         * reloading the rules from the configuration file.
4683
         */
4684
        if ((flags & CGFLAG_USECACHE) && (rl.head == NULL)) {
219✔
UNCOV
4685
                cgroup_warn("no cached rules found, trying to reload from %s.\n",
×
4686
                            CGRULES_CONF_FILE);
4687
                ret = cgroup_reload_cached_rules();
×
4688
                if (ret != 0)
×
UNCOV
4689
                        goto finished;
×
4690
        }
4691

4692
        /*
4693
         * If the user did not ask for cached rules, we must parse the
4694
         * configuration to find a matching rule (if one exists).
4695
         * Else, we'll find the first match in the cached list (rl).
4696
         */
4697
        if (!(flags & CGFLAG_USECACHE)) {
219✔
4698
                cgroup_dbg("Not using cached rules for PID %d.\n", pid);
×
UNCOV
4699
                ret = cgroup_parse_rules(false, uid, gid, procname);
×
4700

4701
                /* The configuration file has an error!  We must exit now. */
4702
                if (ret != -1 && ret != 0) {
×
4703
                        cgroup_err("failed to parse the configuration rules\n");
×
UNCOV
4704
                        goto finished;
×
4705
                }
4706

4707
                /* We did not find a matching rule, so we're done. */
4708
                if (ret == 0) {
×
UNCOV
4709
                        cgroup_dbg("No rule found to match PID: %d, UID: %d, GID: %d\n",
×
4710
                                   pid, uid, gid);
UNCOV
4711
                        goto finished;
×
4712
                }
4713

4714
                /* Otherwise, we did match a rule and it's in trl. */
UNCOV
4715
                tmp = trl.head;
×
4716
        } else {
4717
                /* Find the first matching rule in the cached list. */
4718
                tmp = cgroup_find_matching_rule(uid, gid, pid, procname);
219✔
4719
                if (!tmp) {
219✔
4720
                        cgroup_dbg("No rule found to match PID: %d, UID: %d, GID: %d\n",
218✔
4721
                                   pid, uid, gid);
4722
                        ret = 0;
218✔
4723
                        goto finished;
218✔
4724
                }
4725
        }
4726
        cgroup_dbg("Found matching rule %s for PID: %d, UID: %d, GID: %d\n",
1✔
4727
                   tmp->username, pid, uid, gid);
4728

4729
        if (tmp->is_ignore) {
1✔
4730
                /*
4731
                 * This rule has instructed us that this pid is not to be
4732
                 * processed and should be ignored
4733
                 */
4734
                cgroup_dbg("Matching rule is an ignore rule\n");
×
4735
                ret = 0;
×
UNCOV
4736
                goto finished;
×
4737
        }
4738

4739
        /* If we are here, then we found a matching rule, so execute it. */
4740
        do {
4741
                cgroup_dbg("Executing rule %s for PID %d... ", tmp->username, pid);
1✔
4742

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

4818
                newdest[j] = 0;
1✔
4819
                if (strcmp(newdest, tmp->destination) != 0) {
1✔
4820
                        /* Destination tag contains templates */
4821

4822
                        cgroup_dbg("control group %s is template\n", newdest);
×
4823
                        ret = cgroup_create_template_group(newdest, tmp, flags);
×
4824
                        if (ret) {
×
UNCOV
4825
                                cgroup_warn("failed to create cgroup based on template %s\n",
×
4826
                                            newdest);
UNCOV
4827
                                goto finished;
×
4828
                        }
4829
                }
4830

4831
                /* Apply the rule */
4832
                ret = cgroup_change_cgroup_path(newdest, pid,
1✔
4833
                                                (const char * const *)tmp->controllers);
1✔
4834
                if (ret) {
1✔
4835
                        cgroup_warn("failed to apply the rule. Error was: %d\n", ret);
×
UNCOV
4836
                        goto finished;
×
4837
                }
4838
                cgroup_dbg("OK!\n");
1✔
4839

4840
                /*
4841
                 * Now, check for multi-line rules.  As long as the "next"
4842
                 * rule starts with '%', it's actually part of the rule that
4843
                 * we just executed.
4844
                 */
4845
                tmp = tmp->next;
1✔
4846
        } while (tmp && (tmp->username[0] == '%'));
1✔
4847

4848
finished:
1✔
4849
        return ret;
219✔
4850
}
4851

UNCOV
4852
int cgroup_change_cgroup_uid_gid_flags(uid_t uid, gid_t gid, pid_t pid, int flags)
×
4853
{
UNCOV
4854
        return cgroup_change_cgroup_flags(uid, gid, NULL, pid, flags);
×
4855
}
4856

4857
/**
4858
 * Provides backwards-compatibility with older versions of the API.
4859
 * This function is deprecated, and cgroup_change_cgroup_uid_gid_flags()
4860
 * should be used instead.  In fact, this function simply calls the newer
4861
 * one with flags set to 0 (none).
4862
 *        @param uid The UID to match
4863
 *        @param gid The GID to match
4864
 *        @param pid The PID of the process to move
4865
 *        @return 0 on success, > 0 on error
4866
 */
UNCOV
4867
int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid)
×
4868
{
UNCOV
4869
        return cgroup_change_cgroup_uid_gid_flags(uid, gid, pid, 0);
×
4870
}
4871

4872
/**
4873
 * Changes the cgroup of a program based on the path provided.  In this case,
4874
 * the user must already know into which cgroup the task should be placed and
4875
 * no rules will be parsed.
4876
 *
4877
 *  returns 0 on success.
4878
 */
4879
int cgroup_change_cgroup_path(const char *dest, pid_t pid, const char *const controllers[])
48✔
4880
{
4881
        struct dirent *task_dir = NULL;
48✔
4882
        char path[FILENAME_MAX];
4883
        struct cgroup cgrp;
4884
        int nr, ret;
4885
        pid_t tid;
4886
        DIR *dir;
4887

4888
        if (!cgroup_initialized) {
48✔
4889
                cgroup_warn("libcgroup is not initialized\n");
×
UNCOV
4890
                return ECGROUPNOTINITIALIZED;
×
4891
        }
4892
        memset(&cgrp, 0, sizeof(struct cgroup));
48✔
4893

4894

4895
        if (is_cgroup_mode_unified() && !controllers) {
48✔
4896
                /*
4897
                 * Do not require the user to pass in an array of controller strings on
4898
                 * cgroup v2 systems.  The hierarchy will be the same regardless of
4899
                 * whether controllers are provided or not.
4900
                 */
4901
                strncpy(cgrp.name, dest, FILENAME_MAX);
1✔
4902
                cgrp.name[FILENAME_MAX-1] = '\0';
1✔
4903
        } else {
4904
                if (!controllers)
47✔
UNCOV
4905
                        return ECGINVAL;
×
4906

4907
                ret = cg_prepare_cgroup(&cgrp, pid, dest, controllers);
47✔
4908
                if (ret)
47✔
UNCOV
4909
                        return ret;
×
4910
        }
4911

4912
        /* Add process to cgroup */
4913
        ret = cgroup_attach_task_pid(&cgrp, pid);
48✔
4914
        if (ret) {
48✔
4915
                cgroup_warn("cgroup_attach_task_pid failed: %d\n", ret);
4✔
4916
                goto finished;
4✔
4917
        }
4918

4919
        /* Add all threads to cgroup */
4920
        snprintf(path, FILENAME_MAX, "/proc/%d/task/", pid);
44✔
4921
        dir = opendir(path);
44✔
4922
        if (!dir) {
44✔
4923
                last_errno = errno;
×
4924
                ret = ECGOTHER;
×
UNCOV
4925
                goto finished;
×
4926
        }
4927

4928
        while ((task_dir = readdir(dir)) != NULL) {
182✔
4929
                nr = sscanf(task_dir->d_name, "%i", &tid);
138✔
4930
                if (nr < 1)
138✔
4931
                        continue;
88✔
4932

4933
                if (tid == pid)
50✔
4934
                        continue;
44✔
4935

4936
                ret = cgroup_attach_task_pid(&cgrp, tid);
6✔
4937
                if (ret) {
6✔
4938
                        cgroup_warn("cgroup_attach_task_pid failed: %d\n", ret);
×
UNCOV
4939
                        break;
×
4940
                }
4941
        }
4942

4943
        closedir(dir);
44✔
4944

4945
finished:
48✔
4946
        cgroup_free_controllers(&cgrp);
48✔
4947

4948
        return ret;
48✔
4949
}
4950

4951
/**
4952
 * Changes the cgroup of all running PIDs based on the rules in the config file.
4953
 * If a rules exists for a PID, then the PID is placed in the correct group.
4954
 *
4955
 * This function may be called after creating new control groups to move
4956
 * running PIDs into the newly created control groups.
4957
 *        @return 0 on success, < 0 on error
4958
 */
4959
int cgroup_change_all_cgroups(void)
1✔
4960
{
4961
        struct dirent *pid_dir = NULL;
1✔
4962
        char *path = "/proc/";
1✔
4963
        DIR *dir;
4964

4965
        dir = opendir(path);
1✔
4966
        if (!dir)
1✔
UNCOV
4967
                return -ECGOTHER;
×
4968

4969
        while ((pid_dir = readdir(dir)) != NULL) {
248✔
4970
                int err, pid;
4971
                uid_t euid;
4972
                gid_t egid;
4973
                char *procname = NULL;
247✔
4974

4975
                err = sscanf(pid_dir->d_name, "%i", &pid);
247✔
4976
                if (err < 1)
247✔
4977
                        continue;
65✔
4978

4979
                err = cgroup_get_uid_gid_from_procfs(pid, &euid, &egid);
182✔
4980
                if (err)
182✔
UNCOV
4981
                        continue;
×
4982

4983
                err = cgroup_get_procname_from_procfs(pid, &procname);
182✔
4984
                if (err)
182✔
UNCOV
4985
                        continue;
×
4986

4987
                err = cgroup_change_cgroup_flags(euid, egid, procname, pid, CGFLAG_USECACHE);
182✔
4988
                if (err)
182✔
UNCOV
4989
                        cgroup_dbg("cgroup change pid %i failed\n", pid);
×
4990

4991
                free(procname);
182✔
4992
        }
4993

4994
        closedir(dir);
1✔
4995
        return 0;
1✔
4996
}
4997

4998
/**
4999
 * Print the cached rules table.  This function should be called only after
5000
 * first calling cgroup_parse_config(), but it will work with an empty rule
5001
 * list.
5002
 *        @param fp The file stream to print to
5003
 */
5004
void cgroup_print_rules_config(FILE *fp)
1✔
5005
{
5006
        /* Iterator */
5007
        struct cgroup_rule *itr = NULL;
1✔
5008

5009
        /* Loop variable */
5010
        int i = 0;
1✔
5011

5012
        pthread_rwlock_rdlock(&rl_lock);
1✔
5013

5014
        if (!(rl.head)) {
1✔
5015
                fprintf(fp, "The rules table is empty.\n\n");
×
5016
                pthread_rwlock_unlock(&rl_lock);
×
UNCOV
5017
                return;
×
5018
        }
5019

5020
        itr = rl.head;
1✔
5021
        while (itr) {
2✔
5022
                fprintf(fp, "Rule: %s", itr->username);
1✔
5023
                if (itr->procname)
1✔
5024
                        fprintf(fp, ":%s", itr->procname);
1✔
5025
                fprintf(fp, "\n");
1✔
5026

5027
                if (itr->uid == CGRULE_WILD)
1✔
5028
                        fprintf(fp, "  UID: any\n");
1✔
5029
                else if (itr->uid == CGRULE_INVALID)
×
UNCOV
5030
                        fprintf(fp, "  UID: N/A\n");
×
5031
                else
UNCOV
5032
                        fprintf(fp, "  UID: %d\n", itr->uid);
×
5033

5034
                if (itr->gid == CGRULE_WILD)
1✔
5035
                        fprintf(fp, "  GID: any\n");
1✔
5036
                else if (itr->gid == CGRULE_INVALID)
×
UNCOV
5037
                        fprintf(fp, "  GID: N/A\n");
×
5038
                else
UNCOV
5039
                        fprintf(fp, "  GID: %d\n", itr->gid);
×
5040

5041
                fprintf(fp, "  DEST: %s\n", itr->destination);
1✔
5042

5043
                fprintf(fp, "  CONTROLLERS:\n");
1✔
5044
                for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
18✔
5045
                        if (itr->controllers[i])
17✔
5046
                                fprintf(fp, "    %s\n", itr->controllers[i]);
1✔
5047
                }
5048
                fprintf(fp, "  OPTIONS:\n");
1✔
5049
                if (itr->is_ignore)
1✔
UNCOV
5050
                        fprintf(fp, "    IS_IGNORE: True\n");
×
5051
                else
5052
                        fprintf(fp, "    IS_IGNORE: False\n");
1✔
5053
                fprintf(fp, "\n");
1✔
5054
                itr = itr->next;
1✔
5055
        }
5056
        pthread_rwlock_unlock(&rl_lock);
1✔
5057
}
5058

5059
/**
5060
 * Reloads the rules list, using the given configuration file.
5061
 * This function is probably NOT thread safe (calls cgroup_parse_rules()).
5062
 *        @return 0 on success, > 0 on failure
5063
 */
UNCOV
5064
int cgroup_reload_cached_rules(void)
×
5065
{
5066
        /* Return codes */
UNCOV
5067
        int ret = 0;
×
5068

5069
        cgroup_dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE);
×
5070
        ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL);
×
5071
        if (ret) {
×
5072
                cgroup_warn("error parsing configuration file '%s': %d\n", CGRULES_CONF_FILE, ret);
×
5073
                ret = ECGRULESPARSEFAIL;
×
UNCOV
5074
                goto finished;
×
5075
        }
5076

5077
        #ifdef CGROUP_DEBUG
5078
        cgroup_print_rules_config(stdout);
5079
        #endif
5080
finished:
×
UNCOV
5081
        return ret;
×
5082
}
5083

5084
/**
5085
 * Initializes the rules cache.
5086
 *        @return 0 on success, > 0 on error
5087
 */
5088
int cgroup_init_rules_cache(void)
1✔
5089
{
5090
        /* Return codes */
5091
        int ret = 0;
1✔
5092

5093
        /* Attempt to read the configuration file and cache the rules. */
5094
        ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL);
1✔
5095
        if (ret)
1✔
UNCOV
5096
                cgroup_dbg("Could not initialize rule cache, error was: %d\n", ret);
×
5097

5098
        return ret;
1✔
5099
}
5100

5101
/**
5102
 * cgroup_get_current_controller_path
5103
 * @pid: pid of the current process for which the path is to be determined
5104
 * @controller: name of the controller for which to determine current path
5105
 * @current_path: a pointer that is filled with the value of the current
5106
 *                path as seen in /proc/<pid>/cgroup
5107
 */
5108
int cgroup_get_current_controller_path(pid_t pid, const char *controller, char **current_path)
7✔
5109
{
5110
        enum cg_version_t version;
5111
        enum cg_setup_mode_t mode;
5112
        FILE *pid_cgrp_fd = NULL;
7✔
5113
        bool unified = false;
7✔
5114
        char *path = NULL;
7✔
5115
        int ret;
5116

5117
        if (!cgroup_initialized) {
7✔
5118
                cgroup_warn("libcgroup is not initialized\n");
×
UNCOV
5119
                return ECGROUPNOTINITIALIZED;
×
5120
        }
5121

5122
        if (!current_path)
7✔
UNCOV
5123
                return ECGOTHER;
×
5124

5125
        mode = cgroup_setup_mode();
7✔
5126
        if (mode == CGROUP_MODE_LEGACY && !controller)
7✔
UNCOV
5127
                return ECGOTHER;
×
5128

5129
        /*
5130
         * both unified/hybrid can have the controller mounted as
5131
         * cgroup v2 version.
5132
         */
5133
        if  (!controller) {
7✔
5134
                unified = true;
4✔
5135
        } else {
5136
                ret = cgroup_get_controller_version(controller, &version);
3✔
5137
                if (ret) {
3✔
5138
                        cgroup_warn("Failed to get version of the controller: %s\n", controller);
1✔
5139
                        ret = ECGINVAL;
1✔
5140
                        goto cleanup_path;
1✔
5141
                }
5142
                unified = (version == CGROUP_V2);
2✔
5143
        }
5144

5145
        ret = asprintf(&path, "/proc/%d/cgroup", pid);
6✔
5146
        if (ret <= 0) {
6✔
5147
                cgroup_warn("cannot allocate memory (/proc/pid/cgroup) ret %d\n", ret);
×
UNCOV
5148
                return ret;
×
5149
        }
5150

5151
        ret = ECGROUPNOTEXIST;
6✔
5152
        pid_cgrp_fd = fopen(path, "re");
6✔
5153
        if (!pid_cgrp_fd)
6✔
UNCOV
5154
                goto cleanup_path;
×
5155

5156
        /*
5157
         * Why do we grab the cg_mount_table_lock?, the reason is that the
5158
         * cgroup of a pid can change via the cgroup_attach_task_pid() call.
5159
         * To make sure, we return consistent and safe results, we acquire the
5160
         * lock upfront. We can optimize by acquiring and releasing
5161
         * the lock in the while loop, but that will be more expensive.
5162
         */
5163
        pthread_rwlock_rdlock(&cg_mount_table_lock);
6✔
5164
        while (!feof(pid_cgrp_fd)) {
6✔
5165
                char controllers[FILENAME_MAX];
5166
                char cgrp_path[FILENAME_MAX];
5167
                char *savedptr;
5168
                char *token;
5169
                int num;
5170

5171
                /*
5172
                 * with unified mode, the /proc/pid/cgroup the output is
5173
                 * similar to that of cgroup legacy and hybrid modes:
5174
                 * hierarchy-ID:controller-list:cgroup-path
5175
                 *
5176
                 * the difference is that in cgroup v2:
5177
                 * - hierarchy-ID is always 0 (one hierarchy allowed)
5178
                 * - controller-list is empty
5179
                 */
5180
                if (mode == CGROUP_MODE_UNIFIED || unified) {
6✔
5181
                        ret = fscanf(pid_cgrp_fd, "%d::%4096s\n", &num, cgrp_path);
6✔
5182
                        if (ret != 2) {
6✔
5183
                                /*
5184
                                 * we are interested only in unified format
5185
                                 * line, skip this line.
5186
                                 */
5187
                                if (unified) {
×
5188
                                        ret = fscanf(pid_cgrp_fd, "%*[^\n]\n");
×
5189
                                        if (ret == 0)
×
UNCOV
5190
                                                continue;
×
5191

5192
                                        if (ret == EOF) {
×
5193
                                                last_errno = errno;
×
UNCOV
5194
                                                ret = ECGEOF;
×
5195
                                                goto done;
6✔
5196
                                        }
5197
                                }
5198

5199
                                cgroup_warn("read failed for pid_cgroup_fd ret %d\n", ret);
×
5200
                                last_errno = errno;
×
5201
                                ret = ECGOTHER;
×
UNCOV
5202
                                goto done;
×
5203
                        }
5204

5205
                        /* check if the controller is enabled in cgroup v2 */
5206
                        if (controller) {
6✔
5207
                                ret = cgroupv2_controller_enabled(cgrp_path, controller);
2✔
5208
                                if (ret)
2✔
5209
                                        goto done;
1✔
5210
                        }
5211

5212
                        *current_path = strdup(cgrp_path);
5✔
5213
                        if (!*current_path) {
5✔
5214
                                last_errno = errno;
×
5215
                                ret = ECGOTHER;
×
UNCOV
5216
                                goto done;
×
5217
                        }
5218
                        ret = 0;
5✔
5219
                        goto done;
5✔
5220
                }
5221

5222
                /*
5223
                 * 4096 == FILENAME_MAX, keeping the coverity happy with precision
5224
                 * for the cgrp_path.
5225
                 */
UNCOV
5226
                ret = fscanf(pid_cgrp_fd, "%d:%[^:]:%4096s\n", &num, controllers, cgrp_path);
×
5227
                /*
5228
                 * Magic numbers like "3" seem to be integrating into my daily
5229
                 * life, I need some magic to help make them disappear :)
5230
                 */
5231
                if (ret != 3) {
×
5232
                        cgroup_warn("read failed for pid_cgrp_fd ret %d\n", ret);
×
5233
                        last_errno = errno;
×
5234
                        ret = ECGOTHER;
×
UNCOV
5235
                        goto done;
×
5236
                }
5237

5238
                token = strtok_r(controllers, ",", &savedptr);
×
5239
                while (token) {
×
5240
                        if (strncmp(controller, token, strlen(controller) + 1) == 0) {
×
5241
                                *current_path = strdup(cgrp_path);
×
5242
                                if (!*current_path) {
×
5243
                                        last_errno = errno;
×
5244
                                        ret = ECGOTHER;
×
UNCOV
5245
                                        goto done;
×
5246
                                }
5247
                                ret = 0;
×
UNCOV
5248
                                goto done;
×
5249
                        }
UNCOV
5250
                        token = strtok_r(NULL, ",", &savedptr);
×
5251
                }
5252
        }
5253

UNCOV
5254
done:
×
5255
        pthread_rwlock_unlock(&cg_mount_table_lock);
6✔
5256
        fclose(pid_cgrp_fd);
6✔
5257
cleanup_path:
7✔
5258
        free(path);
7✔
5259

5260
        return ret;
7✔
5261
}
5262

5263
const char *cgroup_strerror(int code)
40✔
5264
{
5265
        int idx = code % ECGROUPNOTCOMPILED;
40✔
5266

5267
        if (code == ECGOTHER) {
40✔
5268
#ifdef STRERROR_R_CHAR_P
5269
                return strerror_r(cgroup_get_last_errno(), errtext, MAXLEN);
12✔
5270
#else
5271
                return strerror_r(cgroup_get_last_errno(), errtext, sizeof(errtext)) ?
5272
                        "unknown error" : errtext;
5273
#endif
5274
        }
5275
        if (idx >= ARRAY_SIZE(cgroup_strerror_codes))
28✔
UNCOV
5276
                return "Invalid error code";
×
5277

5278
        return cgroup_strerror_codes[idx];
28✔
5279
}
5280

5281
/**
5282
 * Return last errno, which caused ECGOTHER error.
5283
 */
5284
int cgroup_get_last_errno(void)
12✔
5285
{
5286
        return last_errno;
12✔
5287
}
5288

5289
static int cg_walk_node(FTS *fts, FTSENT *ent, const int depth, struct cgroup_file_info *info,
8,467✔
5290
                        int dir)
5291
{
5292
        int ret = 0;
8,467✔
5293

5294
        if (!cgroup_initialized)
8,467✔
UNCOV
5295
                return ECGROUPNOTINITIALIZED;
×
5296

5297
        cgroup_dbg("seeing file %s\n", ent->fts_path);
8,467✔
5298

5299
        info->path = ent->fts_name;
8,467✔
5300
        info->parent = ent->fts_parent->fts_name;
8,467✔
5301
        info->full_path = ent->fts_path;
8,467✔
5302
        info->depth = ent->fts_level;
8,467✔
5303
        info->type = CGROUP_FILE_TYPE_OTHER;
8,467✔
5304

5305
        if (depth && (info->depth > depth))
8,467✔
UNCOV
5306
                return 0;
×
5307

5308
        switch (ent->fts_info) {
8,467✔
UNCOV
5309
        case FTS_DNR:
×
5310
        case FTS_ERR:
5311
                errno = ent->fts_errno;
×
UNCOV
5312
                break;
×
5313
        case FTS_D:
168✔
5314
                if (dir & CGROUP_WALK_TYPE_PRE_DIR)
168✔
5315
                        info->type = CGROUP_FILE_TYPE_DIR;
124✔
5316
                break;
168✔
5317
        case FTS_DC:
168✔
5318
        case FTS_NSOK:
5319
        case FTS_NS:
5320
        case FTS_DP:
5321
                if (dir & CGROUP_WALK_TYPE_POST_DIR)
168✔
5322
                        info->type = CGROUP_FILE_TYPE_DIR;
71✔
5323
                break;
168✔
5324
        case FTS_F:
8,131✔
5325
                info->type = CGROUP_FILE_TYPE_FILE;
8,131✔
5326
                break;
8,131✔
5327
        case FTS_DEFAULT:
×
UNCOV
5328
                break;
×
5329
        }
5330
        return ret;
8,467✔
5331
}
5332

5333
int cgroup_walk_tree_next(int depth, void **handle, struct cgroup_file_info *info, int base_level)
8,467✔
5334
{
5335
        struct cgroup_tree_handle *entry;
5336
        FTSENT *ent;
5337
        int ret = 0;
8,467✔
5338

5339
        if (!cgroup_initialized)
8,467✔
UNCOV
5340
                return ECGROUPNOTINITIALIZED;
×
5341

5342
        if (!handle)
8,467✔
UNCOV
5343
                return ECGINVAL;
×
5344

5345
        entry = (struct cgroup_tree_handle *) *handle;
8,467✔
5346

5347
        ent = fts_read(entry->fts);
8,467✔
5348
        if (!ent)
8,467✔
5349
                return ECGEOF;
41✔
5350
        if (!base_level && depth)
8,426✔
UNCOV
5351
                base_level = ent->fts_level + depth;
×
5352

5353
        ret = cg_walk_node(entry->fts, ent, base_level, info, entry->flags);
8,426✔
5354

5355
        *handle = entry;
8,426✔
5356
        return ret;
8,426✔
5357
}
5358

5359
int cgroup_walk_tree_end(void **handle)
41✔
5360
{
5361
        struct cgroup_tree_handle *entry;
5362

5363
        if (!cgroup_initialized)
41✔
UNCOV
5364
                return ECGROUPNOTINITIALIZED;
×
5365

5366
        if (!handle)
41✔
UNCOV
5367
                return ECGINVAL;
×
5368

5369
        entry = (struct cgroup_tree_handle *) *handle;
41✔
5370

5371
        fts_close(entry->fts);
41✔
5372
        free(entry);
41✔
5373
        *handle = NULL;
41✔
5374

5375
        return 0;
41✔
5376
}
5377

5378
/* TODO: Need to decide a better place to put this function. */
5379
int cgroup_walk_tree_begin(const char *controller, const char *base_path, int depth, void **handle,
41✔
5380
                           struct cgroup_file_info *info, int *base_level)
5381
{
5382
        struct cgroup_tree_handle *entry;
5383
        char full_path[FILENAME_MAX];
5384
        char *cg_path[2];
5385
        FTSENT *ent;
5386
        int ret = 0;
41✔
5387

5388
        if (!cgroup_initialized)
41✔
UNCOV
5389
                return ECGROUPNOTINITIALIZED;
×
5390

5391
        if (!handle)
41✔
UNCOV
5392
                return ECGINVAL;
×
5393

5394
        cgroup_dbg("path is %s\n", base_path);
41✔
5395

5396
        if (!cg_build_path(base_path, full_path, controller))
41✔
UNCOV
5397
                return ECGOTHER;
×
5398

5399
        entry = calloc(1, sizeof(struct cgroup_tree_handle));
41✔
5400

5401
        if (!entry) {
41✔
5402
                last_errno = errno;
×
5403
                *handle = NULL;
×
UNCOV
5404
                return ECGOTHER;
×
5405
        }
5406

5407
        entry->flags |= CGROUP_WALK_TYPE_PRE_DIR;
41✔
5408

5409
        *base_level = 0;
41✔
5410
        cg_path[0] = full_path;
41✔
5411
        cg_path[1] = NULL;
41✔
5412

5413
        entry->fts = fts_open(cg_path, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
41✔
5414
        if (entry->fts == NULL) {
41✔
5415
                free(entry);
×
5416
                last_errno = errno;
×
5417
                *handle = NULL;
×
UNCOV
5418
                return ECGOTHER;
×
5419
        }
5420
        ent = fts_read(entry->fts);
41✔
5421
        if (!ent) {
41✔
5422
                cgroup_warn("fts_read failed\n");
×
5423
                fts_close(entry->fts);
×
5424
                free(entry);
×
5425
                *handle = NULL;
×
UNCOV
5426
                return ECGINVAL;
×
5427
        }
5428
        if (!*base_level && depth)
41✔
UNCOV
5429
                *base_level = ent->fts_level + depth;
×
5430

5431
        ret = cg_walk_node(entry->fts, ent, *base_level, info, entry->flags);
41✔
5432
        if (ret != 0) {
41✔
5433
                fts_close(entry->fts);
×
5434
                free(entry);
×
UNCOV
5435
                *handle = NULL;
×
5436
        } else {
5437
                *handle = entry;
41✔
5438
        }
5439
        return ret;
41✔
5440
}
5441

5442
int cgroup_walk_tree_set_flags(void **handle, int flags)
27✔
5443
{
5444
        struct cgroup_tree_handle *entry;
5445

5446
        if (!cgroup_initialized)
27✔
UNCOV
5447
                return ECGROUPNOTINITIALIZED;
×
5448

5449
        if (!handle)
27✔
UNCOV
5450
                return ECGINVAL;
×
5451

5452
        if ((flags & CGROUP_WALK_TYPE_PRE_DIR) &&
27✔
5453
            (flags & CGROUP_WALK_TYPE_POST_DIR))
×
UNCOV
5454
                return ECGINVAL;
×
5455

5456
        entry = (struct cgroup_tree_handle *) *handle;
27✔
5457
        entry->flags = flags;
27✔
5458

5459
        *handle = entry;
27✔
5460
        return 0;
27✔
5461
}
5462

5463
/*
5464
 * This parses a stat line which is in the form of (name value) pair
5465
 * separated by a space.
5466
 */
UNCOV
5467
static int cg_read_stat(FILE *fp, struct cgroup_stat *cgrp_stat)
×
5468
{
UNCOV
5469
        char *saveptr = NULL;
×
5470
        ssize_t read_bytes;
5471
        char *line = NULL;
×
UNCOV
5472
        size_t len = 0;
×
5473
        char *token;
UNCOV
5474
        int ret = 0;
×
5475

5476
        read_bytes = getline(&line, &len, fp);
×
5477
        if (read_bytes == -1) {
×
5478
                ret = ECGEOF;
×
UNCOV
5479
                goto out_free;
×
5480
        }
5481

5482
        token = strtok_r(line, " ", &saveptr);
×
5483
        if (!token) {
×
5484
                ret = ECGINVAL;
×
UNCOV
5485
                goto out_free;
×
5486
        }
UNCOV
5487
        strncpy(cgrp_stat->name, token, FILENAME_MAX - 1);
×
5488

5489
        token = strtok_r(NULL, " ", &saveptr);
×
5490
        if (!token) {
×
5491
                ret = ECGINVAL;
×
UNCOV
5492
                goto out_free;
×
5493
        }
UNCOV
5494
        strncpy(cgrp_stat->value, token, CG_VALUE_MAX - 1);
×
5495

5496
out_free:
×
UNCOV
5497
        free(line);
×
5498

UNCOV
5499
        return ret;
×
5500
}
5501

5502
int cgroup_read_value_end(void **handle)
155✔
5503
{
5504
        FILE *fp;
5505

5506
        if (!cgroup_initialized)
155✔
UNCOV
5507
                return ECGROUPNOTINITIALIZED;
×
5508

5509
        if (!handle)
155✔
UNCOV
5510
                return ECGINVAL;
×
5511

5512
        fp = (FILE *)*handle;
155✔
5513
        fclose(fp);
155✔
5514
        *handle = NULL;
155✔
5515

5516
        return 0;
155✔
5517
}
5518

5519
int cgroup_read_value_next(void **handle, char *buffer, int max)
227✔
5520
{
5521
        char *ret_c;
5522
        int ret = 0;
227✔
5523
        FILE *fp;
5524

5525
        if (!cgroup_initialized)
227✔
UNCOV
5526
                return ECGROUPNOTINITIALIZED;
×
5527

5528
        if (!buffer || !handle)
227✔
UNCOV
5529
                return ECGINVAL;
×
5530

5531
        fp = (FILE *)*handle;
227✔
5532
        ret_c = fgets(buffer, max, fp);
227✔
5533
        if (ret_c == NULL)
227✔
5534
                ret = ECGEOF;
117✔
5535

5536
        return ret;
227✔
5537
}
5538

5539
int cgroup_read_value_begin(const char * const controller, const char *path,
158✔
5540
                            const char * const name, void **handle, char *buffer, int max)
5541
{
5542
        char stat_file[FILENAME_MAX + sizeof(name)];
5543
        char stat_path[FILENAME_MAX];
5544
        char *ret_c = NULL;
158✔
5545
        int ret = 0;
158✔
5546
        FILE *fp;
5547

5548
        if (!cgroup_initialized)
158✔
UNCOV
5549
                return ECGROUPNOTINITIALIZED;
×
5550

5551
        if (!buffer || !handle)
158✔
UNCOV
5552
                return ECGINVAL;
×
5553

5554
        if (!cg_build_path(path, stat_path, controller))
158✔
UNCOV
5555
                return ECGOTHER;
×
5556

5557
        snprintf(stat_file, sizeof(stat_file), "%s/%s", stat_path, name);
158✔
5558
        fp = fopen(stat_file, "re");
158✔
5559
        if (!fp) {
158✔
5560
                cgroup_warn("fopen failed\n");
3✔
5561
                last_errno = errno;
3✔
5562
                *handle = NULL;
3✔
5563
                return ECGOTHER;
3✔
5564
        }
5565

5566
        ret_c = fgets(buffer, max, fp);
155✔
5567
        if (ret_c == NULL)
155✔
5568
                ret = ECGEOF;
8✔
5569

5570
        *handle = fp;
155✔
5571

5572
        return ret;
155✔
5573
}
5574

UNCOV
5575
int cgroup_read_stats_end(void **handle)
×
5576
{
5577
        FILE *fp;
5578

5579
        if (!cgroup_initialized)
×
UNCOV
5580
                return ECGROUPNOTINITIALIZED;
×
5581

5582
        if (!handle)
×
UNCOV
5583
                return ECGINVAL;
×
5584

5585
        fp = (FILE *)*handle;
×
5586
        if (fp == NULL)
×
UNCOV
5587
                return ECGINVAL;
×
5588

UNCOV
5589
        fclose(fp);
×
5590

UNCOV
5591
        return 0;
×
5592
}
5593

UNCOV
5594
int cgroup_read_stats_next(void **handle, struct cgroup_stat *cgrp_stat)
×
5595
{
5596
        FILE *fp;
UNCOV
5597
        int ret = 0;
×
5598

5599
        if (!cgroup_initialized)
×
UNCOV
5600
                return ECGROUPNOTINITIALIZED;
×
5601

5602
        if (!handle || !cgrp_stat)
×
UNCOV
5603
                return ECGINVAL;
×
5604

5605
        fp = (FILE *)*handle;
×
5606
        ret = cg_read_stat(fp, cgrp_stat);
×
UNCOV
5607
        *handle = fp;
×
5608

UNCOV
5609
        return ret;
×
5610
}
5611

5612
/* TODO: Need to decide a better place to put this function. */
UNCOV
5613
int cgroup_read_stats_begin(const char *controller, const char *path, void **handle,
×
5614
                            struct cgroup_stat *cgrp_stat)
5615
{
5616
        char stat_file[FILENAME_MAX + sizeof(".stat")];
5617
        char stat_path[FILENAME_MAX];
UNCOV
5618
        int ret = 0;
×
5619
        FILE *fp;
5620

5621
        if (!cgroup_initialized)
×
UNCOV
5622
                return ECGROUPNOTINITIALIZED;
×
5623

5624
        if (!cgrp_stat || !handle)
×
UNCOV
5625
                return ECGINVAL;
×
5626

5627
        if (!cg_build_path(path, stat_path, controller))
×
UNCOV
5628
                return ECGOTHER;
×
5629

UNCOV
5630
        snprintf(stat_file, sizeof(stat_file), "%s/%s.stat", stat_path, controller);
×
5631

5632
        fp = fopen(stat_file, "re");
×
5633
        if (!fp) {
×
5634
                cgroup_warn("fopen failed\n");
×
UNCOV
5635
                return ECGINVAL;
×
5636
        }
5637

5638
        ret = cg_read_stat(fp, cgrp_stat);
×
UNCOV
5639
        *handle = fp;
×
5640

UNCOV
5641
        return ret;
×
5642
}
5643

UNCOV
5644
int cgroup_get_task_end(void **handle)
×
5645
{
5646
        if (!cgroup_initialized)
×
UNCOV
5647
                return ECGROUPNOTINITIALIZED;
×
5648

5649
        if (!*handle)
×
UNCOV
5650
                return ECGINVAL;
×
5651

5652
        fclose((FILE *) *handle);
×
UNCOV
5653
        *handle = NULL;
×
5654

UNCOV
5655
        return 0;
×
5656
}
5657

UNCOV
5658
int cgroup_get_task_next(void **handle, pid_t *pid)
×
5659
{
5660
        int ret;
5661

5662
        if (!cgroup_initialized)
×
UNCOV
5663
                return ECGROUPNOTINITIALIZED;
×
5664

5665
        if (!handle)
×
UNCOV
5666
                return ECGINVAL;
×
5667

UNCOV
5668
        ret = fscanf((FILE *) *handle, "%u", pid);
×
5669

5670
        if (ret != 1) {
×
5671
                if (ret == EOF)
×
5672
                        return ECGEOF;
×
5673
                last_errno = errno;
×
UNCOV
5674
                return ECGOTHER;
×
5675
        }
5676

UNCOV
5677
        return 0;
×
5678
}
5679

UNCOV
5680
int cgroup_get_task_begin(const char *cgrp, const char *controller, void **handle, pid_t *pid)
×
5681
{
5682
        char path[FILENAME_MAX];
5683
        char *fullpath = NULL;
×
UNCOV
5684
        int ret = 0;
×
5685

5686
        if (!cgroup_initialized)
×
UNCOV
5687
                return ECGROUPNOTINITIALIZED;
×
5688

5689
        if (!cg_build_path(cgrp, path, controller))
×
UNCOV
5690
                return ECGOTHER;
×
5691

UNCOV
5692
        ret = asprintf(&fullpath, "%s/tasks", path);
×
5693

5694
        if (ret < 0) {
×
5695
                last_errno = errno;
×
UNCOV
5696
                return ECGOTHER;
×
5697
        }
5698

5699
        *handle = (void *) fopen(fullpath, "re");
×
UNCOV
5700
        free(fullpath);
×
5701

5702
        if (!*handle) {
×
5703
                last_errno = errno;
×
UNCOV
5704
                return ECGOTHER;
×
5705
        }
UNCOV
5706
        ret = cgroup_get_task_next(handle, pid);
×
5707

UNCOV
5708
        return ret;
×
5709
}
5710

5711
int cgroup_get_controller_end(void **handle)
8✔
5712
{
5713
        int *pos = (int *) *handle;
8✔
5714

5715
        if (!cgroup_initialized)
8✔
UNCOV
5716
                return ECGROUPNOTINITIALIZED;
×
5717

5718
        if (!pos)
8✔
UNCOV
5719
                return ECGINVAL;
×
5720

5721
        free(pos);
8✔
5722
        *handle = NULL;
8✔
5723

5724
        return 0;
8✔
5725
}
5726

5727
int cgroup_get_controller_next(void **handle, struct cgroup_mount_point *info)
72✔
5728
{
5729
        int *pos = (int *) *handle;
72✔
5730
        int ret = 0;
72✔
5731

5732
        if (!cgroup_initialized)
72✔
UNCOV
5733
                return ECGROUPNOTINITIALIZED;
×
5734

5735
        if (!pos)
72✔
UNCOV
5736
                return ECGINVAL;
×
5737

5738
        if (!info)
72✔
UNCOV
5739
                return ECGINVAL;
×
5740

5741
        pthread_rwlock_rdlock(&cg_mount_table_lock);
72✔
5742

5743
        if (cg_mount_table[*pos].name[0] == '\0') {
72✔
5744
                ret = ECGEOF;
×
UNCOV
5745
                goto out_unlock;
×
5746
        }
5747

5748
        if (strncmp(cg_mount_table[*pos].name, CGRP_FILE_PREFIX, CONTROL_NAMELEN_MAX) == 0)
72✔
5749
                /*
5750
                 * For now, hide the "cgroup" pseudo-controller from the user.  This may be
5751
                 * worth revisiting in the future.
5752
                 */
5753
                (*pos)++;
8✔
5754

5755
        if (cg_mount_table[*pos].name[0] == '\0') {
72✔
5756
                ret = ECGEOF;
8✔
5757
                goto out_unlock;
8✔
5758
        }
5759

5760
        strncpy(info->name, cg_mount_table[*pos].name, FILENAME_MAX - 1);
64✔
5761
        info->name[FILENAME_MAX - 1] = '\0';
64✔
5762

5763
        strncpy(info->path, cg_mount_table[*pos].mount.path, FILENAME_MAX - 1);
64✔
5764
        info->path[FILENAME_MAX - 1] = '\0';
64✔
5765

5766
        (*pos)++;
64✔
5767
        *handle = pos;
64✔
5768

5769
out_unlock:
72✔
5770
        pthread_rwlock_unlock(&cg_mount_table_lock);
72✔
5771

5772
        return ret;
72✔
5773
}
5774

5775
int cgroup_get_controller_begin(void **handle, struct cgroup_mount_point *info)
8✔
5776
{
5777
        int *pos;
5778

5779
        if (!cgroup_initialized)
8✔
UNCOV
5780
                return ECGROUPNOTINITIALIZED;
×
5781

5782
        if (!info)
8✔
UNCOV
5783
                return ECGINVAL;
×
5784

5785
        pos = malloc(sizeof(int));
8✔
5786

5787
        if (!pos) {
8✔
5788
                last_errno = errno;
×
UNCOV
5789
                return ECGOTHER;
×
5790
        }
5791

5792
        *pos = 0;
8✔
5793

5794
        *handle = pos;
8✔
5795

5796
        return cgroup_get_controller_next(handle, info);
8✔
5797
}
5798

5799
/**
5800
 * Get process data (euid and egid) from /proc/<pid>/status file.
5801
 * @param pid: The process id
5802
 * @param euid: The uid of param pid
5803
 * @param egid: The gid of param pid
5804
 * @return 0 on success, > 0 on error.
5805
 */
5806
int cgroup_get_uid_gid_from_procfs(pid_t pid, uid_t *euid, gid_t *egid)
219✔
5807
{
5808
        char path[FILENAME_MAX];
5809
        uid_t ruid, suid, fsuid;
5810
        gid_t rgid, sgid, fsgid;
5811
        bool found_euid = false;
219✔
5812
        bool found_egid = false;
219✔
5813
        char buf[4092];
5814
        FILE *f;
5815

5816
        sprintf(path, "/proc/%d/status", pid);
219✔
5817
        f = fopen(path, "re");
219✔
5818
        if (!f)
219✔
UNCOV
5819
                return ECGROUPNOTEXIST;
×
5820

5821
        while (fgets(buf, sizeof(buf), f)) {
2,190✔
5822
                if (!strncmp(buf, "Uid:", 4)) {
2,190✔
5823
                        if (sscanf((buf + strlen("Uid:") + 1), "%d%d%d%d",
219✔
5824
                                    &ruid, euid, &suid, &fsuid) != 4)
UNCOV
5825
                                break;
×
5826
                        cgroup_dbg("Scanned proc values are %d %d %d %d\n",
219✔
5827
                                   ruid, *euid, suid, fsuid);
5828
                        found_euid = true;
219✔
5829
                } else if (!strncmp(buf, "Gid:", 4)) {
1,971✔
5830
                        if (sscanf((buf + strlen("Gid:") + 1), "%d%d%d%d",
219✔
5831
                                   &rgid, egid, &sgid, &fsgid) != 4)
UNCOV
5832
                                break;
×
5833
                        cgroup_dbg("Scanned proc values are %d %d %d %d\n",
219✔
5834
                                   rgid, *egid, sgid, fsgid);
5835
                        found_egid = true;
219✔
5836
                }
5837
                if (found_euid && found_egid)
2,190✔
5838
                        break;
219✔
5839
        }
5840
        fclose(f);
219✔
5841

5842
        if (!found_euid || !found_egid) {
219✔
5843
                /*
5844
                 * This method doesn't match the file format of
5845
                 * /proc/<pid>/status. The format has been changed and we
5846
                 * should catch up the change.
5847
                 */
5848
                cgroup_warn("invalid file format of /proc/%d/status\n", pid);
×
UNCOV
5849
                return ECGFAIL;
×
5850
        }
5851
        return 0;
219✔
5852
}
5853

5854
/**
5855
 * Given a pid, this function will return the controllers and cgroups that
5856
 * the pid is a member of. The caller is expected to allocate the
5857
 * controller_list[] and cgroup_list[] arrays as well as null each entry in
5858
 * the arrays.  This function will allocate the necessary memory for each
5859
 * string within the arrays.
5860
 *
5861
 *        @param pid The process id
5862
 *        @param cgrp_list[] An array of char pointers to hold the cgroups
5863
 *        @param controller_list[] An array of char pointers to hold the list
5864
 *               of controllers
5865
 *        @param list_len The size of the arrays
5866
 */
5867
STATIC int cg_get_cgroups_from_proc_cgroups(pid_t pid, char *cgrp_list[],
18✔
5868
                                            char *controller_list[], int list_len)
5869
{
5870
        char path[FILENAME_MAX];
5871
        char *stok_buff = NULL;
18✔
5872
        size_t buff_len;
5873
        char buf[4092];
5874
        int ret = 0;
18✔
5875
        int idx = 0;
18✔
5876
        FILE *f;
5877

5878
#ifdef UNIT_TEST
5879
        sprintf(path, "%s", TEST_PROC_PID_CGROUP_FILE);
18✔
5880
#else
UNCOV
5881
        sprintf(path, "/proc/%d/cgroup", pid);
×
5882
#endif
5883
        f = fopen(path, "re");
18✔
5884
        if (!f)
18✔
UNCOV
5885
                return ECGROUPNOTEXIST;
×
5886

5887
        while (fgets(buf, sizeof(buf), f)) {
71✔
5888
                /*
5889
                 * Each line in /proc/{pid}/cgroup is like the following:
5890
                 *
5891
                 * {cg#}:{controller}:{cgname}
5892
                 *
5893
                 * e.g.
5894
                 * 7:devices:/user.slice
5895
                 */
5896

5897
                /* Read in the cgroup number.  We don't care about it */
5898
                stok_buff = strtok(buf, ":");
54✔
5899
                /* Read in the controller name */
5900
                stok_buff = strtok(NULL, ":");
54✔
5901

5902
                /*
5903
                 * After this point, we have allocated memory.  If we return
5904
                 * an error code after this, it's up to us to free the memory
5905
                 * we allocated
5906
                 */
5907
                controller_list[idx] = strndup(stok_buff, strlen(stok_buff) + 1);
54✔
5908

5909
                /* Read in the cgroup name */
5910
                stok_buff = strtok(NULL, ":");
54✔
5911

5912
                if (stok_buff == NULL) {
54✔
5913
                        /*
5914
                         * An empty controller is reported on some kernels.
5915
                         * It may look like this:
5916
                         * 0::/user.slice/user-1000.slice/session-1.scope
5917
                         *
5918
                         * Ignore this controller and move on.  Note that we
5919
                         * need to free the controller list entry we made.
5920
                         */
5921
                        free(controller_list[idx]);
4✔
5922
                        controller_list[idx] = NULL;
4✔
5923
                        continue;
4✔
5924
                }
5925

5926
                buff_len = strlen(stok_buff);
50✔
5927
                if (stok_buff[buff_len - 1] == '\n')
50✔
5928
                        buff_len--; /* Don't copy the trailing newline char */
38✔
5929

5930
                /* Read in the cgroup name */
5931
                if (buff_len > 1) {
50✔
5932
                        /* Strip off the leading '/' for every cgroup but the root cgroup */
5933
                        cgrp_list[idx] = malloc(buff_len);
34✔
5934
                        if (!cgrp_list[idx]) {
34✔
NEW
5935
                                cgroup_err("malloc failed: %s\n", strerror(errno));
×
NEW
5936
                                fclose(f);
×
NEW
5937
                                return ECGOTHER;
×
5938
                        }
5939
                        snprintf(cgrp_list[idx], buff_len, "%s", &stok_buff[1]);
34✔
5940
                } else {
5941
                        /* Retain the leading '/' since we're in the root cgroup */
5942
                        cgrp_list[idx] = strndup(stok_buff, buff_len);
16✔
5943
                }
5944

5945
                idx++;
50✔
5946
                if (idx >= list_len) {
50✔
5947
                        cgroup_warn("Maximum mount elements reached. Consider increasing ");
1✔
5948
                        cgroup_cont("MAX_MNT_ELEMENTS\n");
1✔
5949
                        break;
1✔
5950
                }
5951
        }
5952
        fclose(f);
18✔
5953
        return ret;
18✔
5954
}
5955

5956
/**
5957
 * Get process name from /proc/<pid>/status file.
5958
 * @param pid: The process id
5959
 * @param pname_status : The process name
5960
 * @return 0 on success, > 0 on error.
5961
 */
5962
static int cg_get_procname_from_proc_status(pid_t pid, char **procname_status)
219✔
5963
{
5964
        char path[FILENAME_MAX];
5965
        int ret = ECGFAIL;
219✔
5966
        char buf[4092];
5967
        FILE *f;
5968
        int len;
5969

5970
        sprintf(path, "/proc/%d/status", pid);
219✔
5971
        f = fopen(path, "re");
219✔
5972
        if (!f)
219✔
UNCOV
5973
                return ECGROUPNOTEXIST;
×
5974

5975
        while (fgets(buf, sizeof(buf), f)) {
219✔
5976
                if (!strncmp(buf, "Name:", 5)) {
219✔
5977
                        len = strlen(buf);
219✔
5978
                        if (buf[len - 1] == '\n')
219✔
5979
                                buf[len - 1] = '\0';
219✔
5980
                        *procname_status = strdup(buf + strlen("Name:") + 1);
219✔
5981
                        if (*procname_status == NULL) {
219✔
UNCOV
5982
                                last_errno = errno;
×
UNCOV
5983
                                ret = ECGOTHER;
×
UNCOV
5984
                                break;
×
5985
                        }
5986
                        ret = 0;
219✔
5987
                        break;
219✔
5988
                }
5989
        }
5990
        fclose(f);
219✔
5991
        return ret;
219✔
5992
}
5993

5994
/**
5995
 * Get process name from /proc/<pid>/cmdline file.
5996
 * This function is mainly for getting a script name (shell, perl, etc).
5997
 * A script name is written into the second or later argument of
5998
 * /proc/<pid>/cmdline. This function gets each argument and
5999
 * compares it to a process name taken from /proc/<pid>/status.
6000
 * @param pid: The process id
6001
 * @param pname_status : The process name taken from /proc/<pid>/status
6002
 * @param pname_cmdline: The process name taken from /proc/<pid>/cmdline
6003
 * @return 0 on success, > 0 on error.
6004
 */
6005
static int cg_get_procname_from_proc_cmdline(pid_t pid, const char *pname_status,
16✔
6006
                                             char **pname_cmdline)
6007
{
6008
        char pid_cwd_path[FILENAME_MAX];
6009
        char pid_cmd_path[FILENAME_MAX];
6010
        char buf_pname[FILENAME_MAX];
6011
        char buf_cwd[FILENAME_MAX];
6012
        int ret = ECGFAIL;
16✔
6013
        int len = 0;
16✔
6014
        int c = 0;
16✔
6015
        FILE *f;
6016

6017
        memset(buf_cwd, '\0', sizeof(buf_cwd));
16✔
6018
        sprintf(pid_cwd_path, "/proc/%d/cwd", pid);
16✔
6019

6020
        if (readlink(pid_cwd_path, buf_cwd, sizeof(buf_cwd)) < 0)
16✔
UNCOV
6021
                return ECGROUPNOTEXIST;
×
6022

6023
        /* readlink doesn't append a null */
6024
        buf_cwd[FILENAME_MAX - 1] = '\0';
16✔
6025

6026
        sprintf(pid_cmd_path, "/proc/%d/cmdline", pid);
16✔
6027
        f = fopen(pid_cmd_path, "re");
16✔
6028
        if (!f)
16✔
UNCOV
6029
                return ECGROUPNOTEXIST;
×
6030

6031
        while (c != EOF) {
246✔
6032
                c = fgetc(f);
245✔
6033
                if ((c != EOF) && (c != '\0') && (len < FILENAME_MAX - 1)) {
245✔
6034
                        buf_pname[len] = c;
226✔
6035
                        len++;
226✔
6036
                        continue;
226✔
6037
                }
6038
                buf_pname[len] = '\0';
19✔
6039

6040
                if (len == FILENAME_MAX - 1)
19✔
UNCOV
6041
                        while ((c != EOF) && (c != '\0'))
×
UNCOV
6042
                                c = fgetc(f);
×
6043

6044
                /*
6045
                 * The taken process name from /proc/<pid>/status is
6046
                 * shortened to 15 characters if it is over. So the name
6047
                 * should be compared by its length.
6048
                 */
6049
                if (strncmp(pname_status, basename(buf_pname), TASK_COMM_LEN - 1)) {
19✔
6050
                        len = 0;
4✔
6051
                        continue;
4✔
6052
                }
6053

6054
                if (buf_pname[0] == '/') {
15✔
6055
                        *pname_cmdline = strdup(buf_pname);
9✔
6056
                        if (*pname_cmdline == NULL) {
9✔
UNCOV
6057
                                last_errno = errno;
×
UNCOV
6058
                                ret = ECGOTHER;
×
UNCOV
6059
                                break;
×
6060
                        }
6061
                        ret = 0;
9✔
6062
                        break;
9✔
6063
                }
6064

6065
                strcat(buf_cwd, "/");
6✔
6066
                strcat(buf_cwd, buf_pname);
6✔
6067
                if (!realpath(buf_cwd, pid_cmd_path)) {
6✔
6068
                        last_errno = errno;
6✔
6069
                        ret = ECGOTHER;
6✔
6070
                        break;
6✔
6071
                }
6072

UNCOV
6073
                *pname_cmdline = strdup(pid_cmd_path);
×
UNCOV
6074
                if (*pname_cmdline == NULL) {
×
UNCOV
6075
                        last_errno = errno;
×
UNCOV
6076
                        ret = ECGOTHER;
×
6077
                        break;
×
6078
                }
6079
                ret = 0;
×
6080
                break;
×
6081
        }
6082
        fclose(f);
16✔
6083
        return ret;
16✔
6084
}
6085

6086
/**
6087
 * Get a process name from /proc file system.
6088
 * This function allocates memory for a process name, writes a process
6089
 * name onto it. So a caller should free the memory when unusing it.
6090
 * @param pid: The process id
6091
 * @param procname: The process name
6092
 * @return 0 on success, > 0 on error.
6093
 */
6094
int cgroup_get_procname_from_procfs(pid_t pid, char **procname)
219✔
6095
{
6096
        char path[FILENAME_MAX];
6097
        char buf[FILENAME_MAX];
6098
        char *pname_cmdline;
6099
        char *pname_status;
6100
        int ret;
6101

6102
        ret = cg_get_procname_from_proc_status(pid, &pname_status);
219✔
6103
        if (ret)
219✔
UNCOV
6104
                return ret;
×
6105

6106
        /* Get the full patch of process name from /proc/<pid>/exe. */
6107
        memset(buf, '\0', sizeof(buf));
219✔
6108
        snprintf(path, FILENAME_MAX, "/proc/%d/exe", pid);
219✔
6109
        if (readlink(path, buf, sizeof(buf)) < 0) {
219✔
6110
                /*
6111
                 * readlink() fails if a kernel thread, and a process name
6112
                 * is taken from /proc/<pid>/status.
6113
                 */
6114
                *procname = pname_status;
130✔
6115
                return 0;
130✔
6116
        }
6117
        /* readlink doesn't append a null */
6118
        buf[FILENAME_MAX - 1] = '\0';
89✔
6119

6120
        if (!strncmp(pname_status, basename(buf), TASK_COMM_LEN - 1)) {
89✔
6121
                /*
6122
                 * The taken process name from /proc/<pid>/status is
6123
                 * shortened to 15 characters if it is over. So the name
6124
                 * should be compared by its length.
6125
                 */
6126
                free(pname_status);
73✔
6127
                *procname = strdup(buf);
73✔
6128
                if (*procname == NULL) {
73✔
UNCOV
6129
                        last_errno = errno;
×
UNCOV
6130
                        return ECGOTHER;
×
6131
                }
6132
                return 0;
73✔
6133
        }
6134

6135
        /*
6136
         * The above strncmp() is not 0 if a shell script, because
6137
         * /proc/<pid>/exe links a shell command (/bin/bash etc.) and the
6138
         * pname_status represents a shell script name. Then the full path
6139
         * of a shell script is taken from /proc/<pid>/cmdline.
6140
         */
6141
        ret = cg_get_procname_from_proc_cmdline(pid, pname_status, &pname_cmdline);
16✔
6142
        if (!ret) {
16✔
6143
                *procname = pname_cmdline;
9✔
6144
                free(pname_status);
9✔
6145
                return 0;
9✔
6146
        }
6147

6148
        /*
6149
         * The above strncmp() is not 0 also if executing a symbolic link,
6150
         * /proc/pid/exe points to real executable name then. Return it as
6151
         * the last resort.
6152
         */
6153
        free(pname_status);
7✔
6154
        *procname = strdup(buf);
7✔
6155
        if (*procname == NULL) {
7✔
UNCOV
6156
                last_errno = errno;
×
UNCOV
6157
                return ECGOTHER;
×
6158
        }
6159

6160
        return 0;
7✔
6161
}
6162

6163
int cgroup_register_unchanged_process(pid_t pid, int flags)
4✔
6164
{
6165
        char buff[sizeof(CGRULE_SUCCESS_STORE_PID)];
6166
        struct sockaddr_un addr;
6167
        size_t ret_len;
6168
        int ret = 1;
4✔
6169
        int sk;
6170

6171
        sk = socket(PF_UNIX, SOCK_STREAM, 0);
4✔
6172
        if (sk < 0)
4✔
UNCOV
6173
                return 1;
×
6174

6175
        memset(&addr, 0, sizeof(addr));
4✔
6176
        addr.sun_family = AF_UNIX;
4✔
6177
        strcpy(addr.sun_path, CGRULE_CGRED_SOCKET_PATH);
4✔
6178

6179
        if (connect(sk, (struct sockaddr *)&addr,
4✔
6180
            sizeof(addr.sun_family) + strlen(CGRULE_CGRED_SOCKET_PATH)) < 0) {
6181
                /* If the daemon does not work, this function returns 0 as success. */
6182
                ret = 0;
4✔
6183
                goto close;
4✔
6184
        }
UNCOV
6185
        if (write(sk, &pid, sizeof(pid)) < 0)
×
UNCOV
6186
                goto close;
×
6187

UNCOV
6188
        if (write(sk, &flags, sizeof(flags)) < 0)
×
6189
                goto close;
×
6190

UNCOV
6191
        ret_len = read(sk, buff, sizeof(buff));
×
6192
        if (ret_len != sizeof(buff))
×
6193
                goto close;
×
6194

6195
        if (strncmp(buff, CGRULE_SUCCESS_STORE_PID, sizeof(buff)))
×
6196
                goto close;
×
6197

UNCOV
6198
        ret = 0;
×
6199
close:
4✔
6200
        close(sk);
4✔
6201

6202
        return ret;
4✔
6203
}
6204

UNCOV
6205
int cgroup_get_subsys_mount_point(const char *controller, char **mount_point)
×
6206
{
UNCOV
6207
        int ret = ECGROUPNOTEXIST;
×
6208
        int i;
6209

UNCOV
6210
        if (!cgroup_initialized)
×
6211
                return ECGROUPNOTINITIALIZED;
×
6212

UNCOV
6213
        if (!controller)
×
6214
                return ECGINVAL;
×
6215

UNCOV
6216
        pthread_rwlock_rdlock(&cg_mount_table_lock);
×
6217
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
×
6218
                if (strncmp(cg_mount_table[i].name, controller, FILENAME_MAX))
×
UNCOV
6219
                        continue;
×
6220

6221
                *mount_point = strdup(cg_mount_table[i].mount.path);
×
6222

6223
                if (!*mount_point) {
×
UNCOV
6224
                        last_errno = errno;
×
6225
                        ret = ECGOTHER;
×
UNCOV
6226
                        goto out_exit;
×
6227
                }
6228

6229
                ret = 0;
×
6230
                break;
×
6231
        }
UNCOV
6232
out_exit:
×
6233
        pthread_rwlock_unlock(&cg_mount_table_lock);
×
6234

UNCOV
6235
        return ret;
×
6236
}
6237

6238
int cgroup_get_all_controller_end(void **handle)
176✔
6239
{
6240
        FILE *proc_cgrp = (FILE *) *handle;
176✔
6241

6242
        if (!proc_cgrp)
176✔
UNCOV
6243
                return ECGINVAL;
×
6244

6245
        fclose(proc_cgrp);
176✔
6246
        *handle = NULL;
176✔
6247

6248
        return 0;
176✔
6249
}
6250

6251
int cgroup_get_all_controller_next(void **handle, struct controller_data *info)
2,640✔
6252
{
6253
        FILE *proc_cgrp = (FILE *) *handle;
2,640✔
6254
        int hierarchy, num_cgrps, enabled;
6255
        char subsys_name[FILENAME_MAX];
6256
        int err = 0;
2,640✔
6257

6258
        if (!proc_cgrp)
2,640✔
UNCOV
6259
                return ECGINVAL;
×
6260

6261
        if (!info)
2,640✔
UNCOV
6262
                return ECGINVAL;
×
6263

6264
        /*
6265
         * check Linux Kernel sources/kernel/cgroup/cgroup.c cgroup_init_early(),
6266
         * MAX_CGROUP_TYPE_NAMELEN check for details on why 32 is used.
6267
         */
6268
        err = fscanf(proc_cgrp, "%32s %d %d %d\n", subsys_name, &hierarchy, &num_cgrps,
2,640✔
6269
                     &enabled);
6270

6271
        if (err != 4)
2,640✔
6272
                return ECGEOF;
176✔
6273

6274
        strncpy(info->name, subsys_name, FILENAME_MAX);
2,464✔
6275
        info->name[FILENAME_MAX-1] = '\0';
2,464✔
6276
        info->hierarchy = hierarchy;
2,464✔
6277
        info->num_cgroups = num_cgrps;
2,464✔
6278
        info->enabled = enabled;
2,464✔
6279

6280
        return 0;
2,464✔
6281
}
6282

6283
int cgroup_get_all_controller_begin(void **handle, struct controller_data *info)
176✔
6284
{
6285
        FILE *proc_cgroup = NULL;
176✔
6286
        char buf[FILENAME_MAX];
6287
        int ret;
6288

6289
        if (!info)
176✔
UNCOV
6290
                return ECGINVAL;
×
6291

6292
        proc_cgroup = fopen("/proc/cgroups", "re");
176✔
6293
        if (!proc_cgroup) {
176✔
6294
                last_errno = errno;
×
UNCOV
6295
                return ECGOTHER;
×
6296
        }
6297

6298
        if (!fgets(buf, FILENAME_MAX, proc_cgroup)) {
176✔
6299
                last_errno = errno;
×
UNCOV
6300
                fclose(proc_cgroup);
×
UNCOV
6301
                *handle = NULL;
×
UNCOV
6302
                return ECGOTHER;
×
6303
        }
6304
        *handle = proc_cgroup;
176✔
6305

6306
        ret = cgroup_get_all_controller_next(handle, info);
176✔
6307
        if (ret != 0) {
176✔
UNCOV
6308
                fclose(proc_cgroup);
×
UNCOV
6309
                *handle = NULL;
×
6310
        }
6311

6312
        return ret;
176✔
6313
}
6314

6315
static int pid_compare(const void *a, const void *b)
84✔
6316
{
6317
        const pid_t *pid1, *pid2;
6318

6319
        pid1 = (pid_t *) a;
84✔
6320
        pid2 = (pid_t *) b;
84✔
6321

6322
        return (*pid1 - *pid2);
84✔
6323
}
6324

6325
/* pids needs to be completely uninitialized so that we can set it up
6326
 *
6327
 * Caller must free up pids.
6328
 */
6329
static int read_pids(char *path, pid_t **pids, int *size)
10✔
6330
{
6331
        int tot_pids = 16;
10✔
6332
        pid_t *tmp_list;
6333
        FILE *pid_file;
6334
        int n = 0;
10✔
6335
        int err;
6336

6337
        pid_file = fopen(path, "r");
10✔
6338
        if (!pid_file) {
10✔
UNCOV
6339
                last_errno = errno;
×
UNCOV
6340
                *pids = NULL;
×
UNCOV
6341
                *size = 0;
×
UNCOV
6342
                if (errno == ENOENT)
×
6343
                        return ECGROUPUNSUPP;
×
6344
                else
6345
                        return ECGOTHER;
×
6346
        }
6347

6348
        /* Keep doubling the memory allocated if needed */
6349
        tmp_list = malloc(sizeof(pid_t) * tot_pids);
10✔
6350
        if (!tmp_list) {
10✔
UNCOV
6351
                last_errno = errno;
×
UNCOV
6352
                fclose(pid_file);
×
UNCOV
6353
                return ECGOTHER;
×
6354
        }
6355

6356
        while (!feof(pid_file)) {
22✔
6357
                while (!feof(pid_file) && n < tot_pids) {
62✔
6358
                        pid_t pid;
6359

6360
                        err = fscanf(pid_file, "%u", &pid);
60✔
6361
                        if (err == EOF)
60✔
6362
                                break;
10✔
6363
                        tmp_list[n] = pid;
50✔
6364
                        n++;
50✔
6365
                }
6366
                if (!feof(pid_file)) {
12✔
6367
                        pid_t *orig_list = tmp_list;
2✔
6368

6369
                        tot_pids *= 2;
2✔
6370
                        tmp_list = realloc(tmp_list, sizeof(pid_t) * tot_pids);
2✔
6371
                        if (!tmp_list) {
2✔
UNCOV
6372
                                last_errno = errno;
×
UNCOV
6373
                                fclose(pid_file);
×
UNCOV
6374
                                free(orig_list);
×
UNCOV
6375
                                *pids = NULL;
×
6376
                                *size = 0;
×
6377
                                return ECGOTHER;
×
6378
                        }
6379
                }
6380
        }
6381
        fclose(pid_file);
10✔
6382

6383
        *size = n;
10✔
6384
        qsort(tmp_list, n, sizeof(pid_t), &pid_compare);
10✔
6385
        *pids = tmp_list;
10✔
6386

6387
        return 0;
10✔
6388
}
6389

6390
int cgroup_get_procs(const char *name, const char *controller, pid_t **pids, int *size)
10✔
6391
{
6392
        char cgroup_path[FILENAME_MAX];
6393

6394
        cg_build_path(name, cgroup_path, controller);
10✔
6395
        strncat(cgroup_path, "/cgroup.procs", FILENAME_MAX-strlen(cgroup_path));
10✔
6396

6397
        return read_pids(cgroup_path, pids, size);
10✔
6398
}
6399

UNCOV
6400
int cgroup_get_threads(const char *name, const char *controller, pid_t **pids, int *size)
×
6401
{
6402
        char cgroup_path[FILENAME_MAX];
6403

6404
        cg_build_path(name, cgroup_path, controller);
×
UNCOV
6405
        strncat(cgroup_path, "/cgroup.threads", FILENAME_MAX-strlen(cgroup_path));
×
6406

UNCOV
6407
        return read_pids(cgroup_path, pids, size);
×
6408
}
6409

6410
int cgroup_dictionary_create(struct cgroup_dictionary **dict,
12✔
6411
                             int flags)
6412
{
6413
        if (!dict)
12✔
UNCOV
6414
                return ECGINVAL;
×
6415

6416
        *dict = (struct cgroup_dictionary *) calloc(1, sizeof(struct cgroup_dictionary));
12✔
6417
        if (!*dict) {
12✔
6418
                last_errno = errno;
×
UNCOV
6419
                return ECGOTHER;
×
6420
        }
6421
        (*dict)->flags = flags;
12✔
6422

6423
        return 0;
12✔
6424
}
6425

6426
int cgroup_dictionary_add(struct cgroup_dictionary *dict, const char *name, const char *value)
24✔
6427
{
6428
        struct cgroup_dictionary_item *it;
6429

6430
        if (!dict)
24✔
UNCOV
6431
                return ECGINVAL;
×
6432

6433
        it = (struct cgroup_dictionary_item *) malloc(sizeof(struct cgroup_dictionary_item));
24✔
6434
        if (!it) {
24✔
6435
                last_errno = errno;
×
UNCOV
6436
                return ECGOTHER;
×
6437
        }
6438

6439
        it->next = NULL;
24✔
6440
        it->name = name;
24✔
6441
        it->value = value;
24✔
6442

6443
        if (dict->tail) {
24✔
6444
                dict->tail->next = it;
12✔
6445
                dict->tail = it;
12✔
6446
        } else {
6447
                /* It is the first item */
6448
                dict->tail = it;
12✔
6449
                dict->head = it;
12✔
6450
        }
6451

6452
        return 0;
24✔
6453
}
6454

6455
int cgroup_dictionary_free(struct cgroup_dictionary *dict)
16✔
6456
{
6457
        struct cgroup_dictionary_item *it;
6458

6459
        if (!dict)
16✔
6460
                return ECGINVAL;
4✔
6461

6462
        it = dict->head;
12✔
6463
        while (it) {
36✔
6464
                struct cgroup_dictionary_item *del = it;
24✔
6465

6466
                it = it->next;
24✔
6467
                if (!(dict->flags & CG_DICT_DONT_FREE_ITEMS)) {
24✔
6468
                        free((void *)del->value);
24✔
6469
                        free((void *)del->name);
24✔
6470
                }
6471
                free(del);
24✔
6472
        }
6473
        free(dict);
12✔
6474

6475
        return 0;
12✔
6476
}
6477

6478
int cgroup_dictionary_iterator_begin(struct cgroup_dictionary *dict, void **handle,
12✔
6479
                                     const char **name, const char **value)
6480
{
6481
        struct cgroup_dictionary_iterator *iter;
6482

6483
        *handle = NULL;
12✔
6484

6485
        if (!dict)
12✔
UNCOV
6486
                return ECGINVAL;
×
6487

6488
        iter = (struct cgroup_dictionary_iterator *)
6489
                malloc(sizeof(struct cgroup_dictionary_iterator));
12✔
6490
        if (!iter) {
12✔
UNCOV
6491
                last_errno = errno;
×
UNCOV
6492
                return ECGOTHER;
×
6493
        }
6494

6495
        iter->item = dict->head;
12✔
6496
        *handle = iter;
12✔
6497

6498
        return cgroup_dictionary_iterator_next(handle, name, value);
12✔
6499
}
6500

6501
int cgroup_dictionary_iterator_next(void **handle, const char **name, const char **value)
36✔
6502
{
6503
        struct cgroup_dictionary_iterator *iter;
6504

6505
        if (!handle)
36✔
UNCOV
6506
                return ECGINVAL;
×
6507

6508
        iter = *handle;
36✔
6509

6510
        if (!iter)
36✔
UNCOV
6511
                return ECGINVAL;
×
6512

6513
        if (!iter->item)
36✔
6514
                return ECGEOF;
12✔
6515

6516
        *name = iter->item->name;
24✔
6517
        *value = iter->item->value;
24✔
6518
        iter->item = iter->item->next;
24✔
6519

6520
        return 0;
24✔
6521
}
6522

6523
void cgroup_dictionary_iterator_end(void **handle)
12✔
6524
{
6525
        if (!handle)
12✔
UNCOV
6526
                return;
×
6527

6528
        free(*handle);
12✔
6529
        *handle = NULL;
12✔
6530
}
6531

UNCOV
6532
int cgroup_get_subsys_mount_point_begin(const char *controller, void **handle, char *path)
×
6533
{
6534
        int i;
6535

6536
        if (!cgroup_initialized)
×
UNCOV
6537
                return ECGROUPNOTINITIALIZED;
×
UNCOV
6538
        if (!handle || !path || !controller)
×
UNCOV
6539
                return ECGINVAL;
×
6540

6541
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++)
×
6542
                if (strcmp(controller, cg_mount_table[i].name) == 0)
×
6543
                        break;
×
6544

6545
        if (cg_mount_table[i].name[0] == '\0') {
×
6546
                /* The controller is not mounted at all */
6547
                *handle = NULL;
×
UNCOV
6548
                *path = '\0';
×
6549
                return ECGEOF;
×
6550
        }
6551

6552
        /*
6553
         * 'handle' is pointer to struct cg_mount_point, which should be
6554
         * returned next.
6555
         */
UNCOV
6556
        *handle = cg_mount_table[i].mount.next;
×
UNCOV
6557
        strcpy(path, cg_mount_table[i].mount.path);
×
6558

UNCOV
6559
        return 0;
×
6560
}
6561

UNCOV
6562
int cgroup_get_subsys_mount_point_next(void **handle, char *path)
×
6563
{
6564
        struct cg_mount_point *it;
6565

6566
        if (!cgroup_initialized)
×
UNCOV
6567
                return ECGROUPNOTINITIALIZED;
×
6568

UNCOV
6569
        if (!handle || !path)
×
6570
                return ECGINVAL;
×
6571

UNCOV
6572
        it = *handle;
×
6573
        if (!it) {
×
6574
                *handle = NULL;
×
UNCOV
6575
                *path = '\0';
×
6576
                return ECGEOF;
×
6577
        }
6578

6579
        *handle = it->next;
×
6580
        strcpy(path, it->path);
×
6581

UNCOV
6582
        return 0;
×
6583
}
6584

UNCOV
6585
int cgroup_get_subsys_mount_point_end(void **handle)
×
6586
{
UNCOV
6587
        if (!cgroup_initialized)
×
UNCOV
6588
                return ECGROUPNOTINITIALIZED;
×
6589

UNCOV
6590
        if (!handle)
×
6591
                return ECGINVAL;
×
6592

UNCOV
6593
        *handle = NULL;
×
6594

6595
        return 0;
×
6596
}
6597

6598
int cgroup_get_controller_version(const char * const controller, enum cg_version_t * const version)
2,541✔
6599
{
6600
        int i;
6601

6602
        if (!version)
2,541✔
UNCOV
6603
                return ECGINVAL;
×
6604

6605
        if (!controller && strlen(cg_cgroup_v2_mount_path) > 0) {
2,541✔
6606
                *version = CGROUP_V2;
56✔
6607
                return 0;
56✔
6608
        }
6609

6610
        if (!controller)
2,485✔
UNCOV
6611
                return ECGINVAL;
×
6612

6613
        *version = CGROUP_UNK;
2,485✔
6614

6615
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
6,331✔
6616
                if (strncmp(cg_mount_table[i].name, controller,
6,328✔
6617
                            sizeof(cg_mount_table[i].name)) == 0) {
6618
                        *version = cg_mount_table[i].version;
2,482✔
6619
                        return 0;
2,482✔
6620
                }
6621
        }
6622

6623
        return ECGROUPNOTEXIST;
3✔
6624
}
6625

6626
static int search_and_append_mnt_path(struct cg_mount_point **mount_point, char *path)
18✔
6627
{
6628
        struct cg_mount_point *mnt_point, *mnt_tmp, *mnt_prev;
6629

6630
        mnt_tmp = *mount_point;
18✔
6631
        while (mnt_tmp) {
18✔
6632
                if (strcmp(mnt_tmp->path, path) == 0)
16✔
6633
                        return ECGVALUEEXISTS;
16✔
6634

UNCOV
6635
                mnt_prev = mnt_tmp;
×
UNCOV
6636
                mnt_tmp = mnt_tmp->next;
×
6637
        }
6638

6639
        mnt_point = malloc(sizeof(struct cg_mount_point));
2✔
6640
        if (mnt_point == NULL) {
2✔
UNCOV
6641
                last_errno = errno;
×
UNCOV
6642
                return ECGOTHER;
×
6643
        }
6644

6645
        strncpy(mnt_point->path, path, FILENAME_MAX - 1);
2✔
6646
        mnt_point->path[FILENAME_MAX - 1] = '\0';
2✔
6647

6648
        mnt_point->next = NULL;
2✔
6649

6650
        if (*mount_point == NULL)
2✔
6651
                *mount_point = mnt_point;
2✔
6652
        else
UNCOV
6653
                mnt_prev->next = mnt_point;
×
6654

6655
        return 0;
2✔
6656
}
6657

6658
/**
6659
 * List the mount paths, that matches the specified version
6660
 *
6661
 *        @param cgrp_version The cgroup type/version
6662
 *        @param mount_paths Holds the list of mount paths
6663
 *        @return 0 success and list of mounts paths in mount_paths
6664
 *                ECGOTHER on failure and mount_paths is NULL.
6665
 */
6666
int cgroup_list_mount_points(const enum cg_version_t cgrp_version, char ***mount_paths)
4✔
6667
{
6668
        struct cg_mount_point *mount_point, *mnt_tmp = NULL;
4✔
6669
        char **mnt_paths = NULL;
4✔
6670
        int i, idx = 0;
4✔
6671
        int ret;
6672

6673
        if (!cgroup_initialized)
4✔
UNCOV
6674
                return ECGROUPNOTINITIALIZED;
×
6675

6676
        if (cgrp_version != CGROUP_V1 && cgrp_version != CGROUP_V2)
4✔
UNCOV
6677
                return ECGINVAL;
×
6678

6679
        pthread_rwlock_rdlock(&cg_mount_table_lock);
4✔
6680

6681
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
40✔
6682
                if (cg_mount_table[i].version != cgrp_version)
36✔
6683
                        continue;
18✔
6684

6685
                mount_point = &cg_mount_table[i].mount;
18✔
6686
                while (mount_point) {
36✔
6687
                        ret = search_and_append_mnt_path(&mnt_tmp, mount_point->path);
18✔
6688
                        if (ret != 0 && ret != ECGVALUEEXISTS)
18✔
UNCOV
6689
                                goto err;
×
6690

6691
                        /* Avoid adding duplicate mount points */
6692
                        if (ret != ECGVALUEEXISTS)
18✔
6693
                                idx++;
2✔
6694

6695
                        mount_point = mount_point->next;
18✔
6696
                }
6697
        }
6698

6699
        /*
6700
         * Cgroup v2 can be mounted without any controller and these mount
6701
         * paths are not part of the cg_mount_table.  Check and append
6702
         * them to mnt_paths.
6703
         */
6704
        if (cgrp_version == CGROUP_V2 && cg_cgroup_v2_empty_mount_paths) {
4✔
UNCOV
6705
                mount_point = cg_cgroup_v2_empty_mount_paths;
×
UNCOV
6706
                while (mount_point) {
×
UNCOV
6707
                        ret = search_and_append_mnt_path(&mnt_tmp, mount_point->path);
×
UNCOV
6708
                        if (ret)
×
6709
                                goto err;
×
6710

6711
                        idx++;
×
6712
                        mount_point = mount_point->next;
×
6713
                }
6714
        }
6715

6716
        mnt_paths = malloc(sizeof(char *) * (idx + 1));
4✔
6717
        if (mnt_paths == NULL) {
4✔
UNCOV
6718
                last_errno = errno;
×
UNCOV
6719
                ret = ECGOTHER;
×
UNCOV
6720
                goto err;
×
6721
        }
6722

6723
        for (i = 0, mount_point = mnt_tmp;
4✔
6724
             mount_point;
6✔
6725
             mount_point = mount_point->next, i++) {
2✔
6726

6727
                mnt_paths[i] = strdup(mount_point->path);
2✔
6728
                if (mnt_paths[i] == NULL) {
2✔
UNCOV
6729
                        last_errno = errno;
×
UNCOV
6730
                        ret = ECGOTHER;
×
UNCOV
6731
                        goto err;
×
6732
                }
6733
        }
6734
        mnt_paths[i] = '\0';
4✔
6735

6736
        ret = 0;
4✔
6737
        *mount_paths = mnt_paths;
4✔
6738

6739
err:
4✔
6740
        pthread_rwlock_unlock(&cg_mount_table_lock);
4✔
6741

6742
        while (mnt_tmp) {
6✔
6743
                mount_point = mnt_tmp;
2✔
6744
                mnt_tmp = mnt_tmp->next;
2✔
6745
                free(mount_point);
2✔
6746
        }
6747

6748
        if (ret != 0 && mnt_paths) {
4✔
UNCOV
6749
                for (i = 0; i < idx; i++)
×
UNCOV
6750
                        free(mnt_paths[i]);
×
UNCOV
6751
                free(mnt_paths);
×
UNCOV
6752
                *mount_paths = NULL;
×
6753
        }
6754

6755
        return ret;
4✔
6756
}
6757

6758
const struct cgroup_library_version *cgroup_version(void)
4✔
6759
{
6760
        return &library_version;
4✔
6761
}
6762

6763
/**
6764
 * Finds the current cgroup setup mode (legacy/unified/hybrid).
6765
 * Returns unknown of failure and setup mode on success.
6766
 */
6767
enum cg_setup_mode_t cgroup_setup_mode(void)
195✔
6768
{
6769
#define CGROUP2_SUPER_MAGIC        0x63677270
6770
#define CGROUP_SUPER_MAGIC        0x27E0EB
6771

6772
        unsigned int cg_setup_mode_bitmask = 0U;
195✔
6773
        enum cg_setup_mode_t setup_mode;
6774
        struct statfs cgrp_buf;
6775
        int i, ret = 0;
195✔
6776

6777
        if (!cgroup_initialized)
195✔
UNCOV
6778
                return ECGROUPNOTINITIALIZED;
×
6779

6780
        setup_mode = CGROUP_MODE_UNK;
195✔
6781

6782
        pthread_rwlock_wrlock(&cg_mount_table_lock);
195✔
6783
        for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
1,950✔
6784
                ret = statfs(cg_mount_table[i].mount.path, &cgrp_buf);
1,755✔
6785
                if (ret) {
1,755✔
UNCOV
6786
                        setup_mode = CGROUP_MODE_UNK;
×
UNCOV
6787
                        cgroup_err("Failed to get stats of '%s'\n", cg_mount_table[i].mount.path);
×
UNCOV
6788
                        goto out;
×
6789
                }
6790

6791
                if (cgrp_buf.f_type == CGROUP2_SUPER_MAGIC)
1,755✔
6792
                        cg_setup_mode_bitmask |= (1U << 0);
1,755✔
UNCOV
6793
                else if (cgrp_buf.f_type == CGROUP_SUPER_MAGIC)
×
UNCOV
6794
                        cg_setup_mode_bitmask |= (1U << 1);
×
6795
        }
6796

6797
        if (cg_cgroup_v2_empty_mount_paths)
195✔
6798
                cg_setup_mode_bitmask |= (1U << 0);
×
6799

6800
        if (cg_setup_mode_bitmask & (1U << 0) && cg_setup_mode_bitmask & (1U << 1))
195✔
UNCOV
6801
                setup_mode = CGROUP_MODE_HYBRID;
×
6802
        else if (cg_setup_mode_bitmask & (1U << 0))
195✔
6803
                setup_mode = CGROUP_MODE_UNIFIED;
195✔
UNCOV
6804
        else if (cg_setup_mode_bitmask & (1U << 1))
×
6805
                setup_mode = CGROUP_MODE_LEGACY;
×
6806

UNCOV
6807
out:
×
6808
        pthread_rwlock_unlock(&cg_mount_table_lock);
195✔
6809
        return setup_mode;
195✔
6810
}
6811

6812
int cgroup_get_controller_count(struct cgroup *cgrp)
25✔
6813
{
6814
        if (!cgrp)
25✔
UNCOV
6815
                return -1;
×
6816

6817
        return cgrp->index;
25✔
6818
}
6819

6820
struct cgroup_controller *cgroup_get_controller_by_index(struct cgroup *cgrp, int index)
83✔
6821
{
6822
        if (!cgrp)
83✔
UNCOV
6823
                return NULL;
×
6824

6825
        if (index >= cgrp->index)
83✔
UNCOV
6826
                return NULL;
×
6827

6828
        return cgrp->controller[index];
83✔
6829
}
6830

6831
char *cgroup_get_controller_name(struct cgroup_controller *controller)
83✔
6832
{
6833
        if (!controller)
83✔
UNCOV
6834
                return NULL;
×
6835

6836
        return controller->name;
83✔
6837
}
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