]> git.proxmox.com Git - mirror_lxcfs.git/blob - cgfs.c
Don't use tasks file to determine access rights to its cgroup
[mirror_lxcfs.git] / cgfs.c
1 /*
2 * Copyright © 2015 Canonical Limited
3 *
4 * Authors:
5 * Serge Hallyn <serge.hallyn@ubuntu.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <dirent.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <grp.h>
32 #include <stdint.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #include <sys/mount.h>
37 #include <sys/wait.h>
38 #include <stdbool.h>
39 #include <cgfs.h>
40
41 /* directory under which we mount the controllers - /run/lxcfs/controllers */
42 char *basedir;
43 #define basedir RUNTIME_PATH "/lxcfs/controllers"
44
45 int is_dir(const char *path)
46 {
47 struct stat statbuf;
48 int ret = stat(path, &statbuf);
49 if (ret == 0 && S_ISDIR(statbuf.st_mode))
50 return 1;
51 return 0;
52 }
53
54 char *must_copy_string(const char *str)
55 {
56 char *dup = NULL;
57 if (!str)
58 return NULL;
59 do {
60 dup = strdup(str);
61 } while (!dup);
62
63 return dup;
64 }
65
66 static inline void drop_trailing_newlines(char *s)
67 {
68 int l;
69
70 for (l=strlen(s); l>0 && s[l-1] == '\n'; l--)
71 s[l-1] = '\0';
72 }
73
74 #define BATCH_SIZE 50
75 static void dorealloc(char **mem, size_t oldlen, size_t newlen)
76 {
77 int newbatches = (newlen / BATCH_SIZE) + 1;
78 int oldbatches = (oldlen / BATCH_SIZE) + 1;
79
80 if (!*mem || newbatches > oldbatches) {
81 char *tmp;
82 do {
83 tmp = realloc(*mem, newbatches * BATCH_SIZE);
84 } while (!tmp);
85 *mem = tmp;
86 }
87 }
88 static void append_line(char **contents, size_t *len, char *line, ssize_t linelen)
89 {
90 size_t newlen = *len + linelen;
91 dorealloc(contents, *len, newlen + 1);
92 memcpy(*contents + *len, line, linelen+1);
93 *len = newlen;
94 }
95
96 static char *read_file(const char *from)
97 {
98 char *line = NULL;
99 char *contents = NULL;
100 FILE *f = fopen(from, "r");
101 size_t len = 0, fulllen = 0;
102 ssize_t linelen;
103
104 if (!f)
105 return NULL;
106
107 while ((linelen = getline(&line, &len, f)) != -1) {
108 append_line(&contents, &fulllen, line, linelen);
109 }
110 fclose(f);
111
112 if (contents)
113 drop_trailing_newlines(contents);
114 free(line);
115 return contents;
116 }
117
118 static bool write_string(const char *fnam, const char *string)
119 {
120 FILE *f;
121 size_t len, ret;
122
123 if (!(f = fopen(fnam, "w")))
124 return false;
125 len = strlen(string);
126 ret = fwrite(string, 1, len, f);
127 if (ret != len) {
128 fprintf(stderr, "Error writing to file: %s\n", strerror(errno));
129 fclose(f);
130 return false;
131 }
132 if (fclose(f) < 0) {
133 fprintf(stderr, "Error writing to file: %s\n", strerror(errno));
134 return false;
135 }
136 return true;
137 }
138
139 static bool store_hierarchy(char *stridx, char *h)
140 {
141 int idx = atoi(stridx);
142 size_t needed_len = (idx + 1) * sizeof(char *);
143
144 if (idx < 0 || idx > 30) {
145 fprintf(stderr, "Error: corrupt /proc/self/cgroup\n");
146 return false;
147 }
148
149 if (!hierarchies) {
150 hierarchies = malloc(needed_len);
151 memset(hierarchies, 0, needed_len);
152 num_hierarchies = idx + 1;
153 } else if (idx >= num_hierarchies) {
154 char **tmp;
155 size_t old_len = (num_hierarchies + 1) * sizeof(char *);
156 do {
157 tmp = malloc(needed_len);
158 } while (!tmp);
159 memset(tmp, 0, needed_len);
160 memcpy(tmp, hierarchies, old_len);
161 free(hierarchies);
162 hierarchies = tmp;
163 num_hierarchies = idx + 1;
164 }
165
166 if (hierarchies[idx]) {
167 fprintf(stderr, "Error: corrupt /proc/self/cgroup\n");
168 return false;
169 }
170 hierarchies[idx] = must_copy_string(h);
171 return true;
172 }
173
174 void print_subsystems(void)
175 {
176 int i;
177
178 fprintf(stderr, "hierarchies:");
179 for (i = 0; i < num_hierarchies; i++) {
180 if (hierarchies[i])
181 fprintf(stderr, " %d: %s\n", i, hierarchies[i]);
182 }
183 }
184
185 static bool collect_subsystems(void)
186 {
187 FILE *f;
188 char *line = NULL;
189 size_t len = 0;
190 bool bret = false;
191
192 if ((f = fopen("/proc/self/cgroup", "r")) == NULL) {
193 fprintf(stderr, "Error opening /proc/self/cgroup: %s\n", strerror(errno));
194 return false;
195 }
196 while (getline(&line, &len, f) != -1) {
197 char *p, *p2;
198
199 p = strchr(line, ':');
200 if (!p)
201 goto out;
202 *(p++) = '\0';
203
204 p2 = strrchr(p, ':');
205 if (!p2)
206 goto out;
207 *p2 = '\0';
208
209 if (!store_hierarchy(line, p))
210 goto out;
211 }
212
213 print_subsystems();
214 bret = true;
215
216 out:
217 free(line);
218 fclose(f);
219 return bret;
220 }
221
222 static bool do_mount_cgroups(void)
223 {
224 int i, ret;
225 char target[MAXPATHLEN];
226
227 for (i=0; i<num_hierarchies; i++) {
228 if (!hierarchies[i])
229 continue;
230 ret = snprintf(target, MAXPATHLEN, "%s/%s", basedir, hierarchies[i]);
231 if (ret < 0 || ret >= MAXPATHLEN)
232 return false;
233 if (mkdir(target, 0755) < 0 && errno != EEXIST)
234 return false;
235 if (mount(hierarchies[i], target, "cgroup", 0, hierarchies[i]) < 0) {
236 fprintf(stderr, "Failed mounting cgroups\n");
237 return false;
238 }
239 }
240
241 return true;
242 }
243
244 static bool umount_if_mounted(void)
245 {
246 if (umount2(basedir, MNT_DETACH) < 0 && errno != EINVAL) {
247 fprintf(stderr, "failed to umount %s: %s\n", basedir,
248 strerror(errno));
249 return false;
250 }
251 return true;
252 }
253
254 static bool mkdir_p(const char *dir, mode_t mode)
255 {
256 const char *tmp = dir;
257 const char *orig = dir;
258 char *makeme;
259
260 do {
261 dir = tmp + strspn(tmp, "/");
262 tmp = dir + strcspn(dir, "/");
263 makeme = strndup(orig, dir - orig);
264 if (*makeme) {
265 if (mkdir(makeme, mode) && errno != EEXIST) {
266 fprintf(stderr, "failed to create directory '%s': %s",
267 makeme, strerror(errno));
268 free(makeme);
269 return false;
270 }
271 }
272 free(makeme);
273 } while(tmp != dir);
274
275 return true;
276 }
277 static bool setup_cgfs_dir(void)
278 {
279 if (!mkdir_p(basedir, 0700)) {
280 fprintf(stderr, "Failed to create lxcfs cgdir\n");
281 return false;
282 }
283 if (!umount_if_mounted()) {
284 fprintf(stderr, "Failed to clean up old lxcfs cgdir\n");
285 return false;
286 }
287 if (mount("tmpfs", basedir, "tmpfs", 0, "size=100000,mode=700") < 0) {
288 fprintf(stderr, "Failed to mount tmpfs for private controllers\n");
289 return false;
290 }
291 return true;
292 }
293
294 bool cgfs_setup_controllers(void)
295 {
296 if (!setup_cgfs_dir()) {
297 return false;
298 }
299
300 if (!collect_subsystems()) {
301 fprintf(stderr, "failed to collect cgroup subsystems\n");
302 return false;
303 }
304
305 if (!do_mount_cgroups()) {
306 fprintf(stderr, "Failed to set up cgroup mounts\n");
307 return false;
308 }
309
310 return true;
311 }
312
313 static bool in_comma_list(const char *needle, const char *haystack)
314 {
315 const char *s = haystack, *e;
316 size_t nlen = strlen(needle);
317
318 while (*s && (e = index(s, ','))) {
319 if (nlen != e - s) {
320 s = e + 1;
321 continue;
322 }
323 if (strncmp(needle, s, nlen) == 0)
324 return true;
325 s = e + 1;
326 }
327 if (strcmp(needle, s) == 0)
328 return true;
329 return false;
330 }
331
332 /* do we need to do any massaging here? I'm not sure... */
333 char *find_mounted_controller(const char *controller)
334 {
335 int i;
336
337 for (i = 0; i < num_hierarchies; i++) {
338 if (!hierarchies[i])
339 continue;
340 if (strcmp(hierarchies[i], controller) == 0)
341 return hierarchies[i];
342 if (in_comma_list(controller, hierarchies[i]))
343 return hierarchies[i];
344 }
345
346 return NULL;
347 }
348
349 bool cgfs_set_value(const char *controller, const char *cgroup, const char *file,
350 const char *value)
351 {
352 size_t len;
353 char *fnam, *tmpc = find_mounted_controller(controller);
354
355 if (!tmpc)
356 return false;
357 /* basedir / tmpc / cgroup / file \0 */
358 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
359 fnam = alloca(len);
360 snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, file);
361
362 return write_string(fnam, value);
363 }
364
365 // Chown all the files in the cgroup directory. We do this when we create
366 // a cgroup on behalf of a user.
367 static void chown_all_cgroup_files(const char *dirname, uid_t uid, gid_t gid)
368 {
369 struct dirent dirent, *direntp;
370 char path[MAXPATHLEN];
371 size_t len;
372 DIR *d;
373 int ret;
374
375 len = strlen(dirname);
376 if (len >= MAXPATHLEN) {
377 fprintf(stderr, "chown_all_cgroup_files: pathname too long: %s\n", dirname);
378 return;
379 }
380
381 d = opendir(dirname);
382 if (!d) {
383 fprintf(stderr, "chown_all_cgroup_files: failed to open %s\n", dirname);
384 return;
385 }
386
387 while (readdir_r(d, &dirent, &direntp) == 0 && direntp) {
388 if (!strcmp(direntp->d_name, ".") || !strcmp(direntp->d_name, ".."))
389 continue;
390 ret = snprintf(path, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
391 if (ret < 0 || ret >= MAXPATHLEN) {
392 fprintf(stderr, "chown_all_cgroup_files: pathname too long under %s\n", dirname);
393 continue;
394 }
395 if (chown(path, uid, gid) < 0)
396 fprintf(stderr, "Failed to chown file %s to %u:%u", path, uid, gid);
397 }
398 closedir(d);
399 }
400
401 int cgfs_create(const char *controller, const char *cg, uid_t uid, gid_t gid)
402 {
403 size_t len;
404 char *dirnam, *tmpc = find_mounted_controller(controller);
405
406 if (!tmpc)
407 return -EINVAL;
408 /* basedir / tmpc / cg \0 */
409 len = strlen(basedir) + strlen(tmpc) + strlen(cg) + 3;
410 dirnam = alloca(len);
411 snprintf(dirnam, len, "%s/%s/%s", basedir,tmpc, cg);
412
413 if (mkdir(dirnam, 0755) < 0)
414 return -errno;
415
416 if (uid == 0 && gid == 0)
417 return 0;
418
419 if (chown(dirnam, uid, gid) < 0)
420 return -errno;
421
422 chown_all_cgroup_files(dirnam, uid, gid);
423
424 return 0;
425 }
426
427 static bool recursive_rmdir(const char *dirname)
428 {
429 struct dirent dirent, *direntp;
430 DIR *dir;
431 bool ret = false;
432 char pathname[MAXPATHLEN];
433
434 dir = opendir(dirname);
435 if (!dir) {
436 fprintf(stderr, "%s: failed to open %s: %s\n", __func__, dirname, strerror(errno));
437 return false;
438 }
439
440 while (!readdir_r(dir, &dirent, &direntp)) {
441 struct stat mystat;
442 int rc;
443
444 if (!direntp)
445 break;
446
447 if (!strcmp(direntp->d_name, ".") ||
448 !strcmp(direntp->d_name, ".."))
449 continue;
450
451 rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
452 if (rc < 0 || rc >= MAXPATHLEN) {
453 fprintf(stderr, "pathname too long\n");
454 continue;
455 }
456
457 ret = lstat(pathname, &mystat);
458 if (ret) {
459 fprintf(stderr, "%s: failed to stat %s: %s\n", __func__, pathname, strerror(errno));
460 continue;
461 }
462 if (S_ISDIR(mystat.st_mode)) {
463 if (!recursive_rmdir(pathname))
464 fprintf(stderr, "Error removing %s\n", pathname);
465 }
466 }
467
468 ret = true;
469 if (closedir(dir) < 0) {
470 fprintf(stderr, "%s: failed to close directory %s: %s\n", __func__, dirname, strerror(errno));
471 ret = false;
472 }
473
474 if (rmdir(dirname) < 0) {
475 fprintf(stderr, "%s: failed to delete %s: %s\n", __func__, dirname, strerror(errno));
476 ret = false;
477 }
478
479 return ret;
480 }
481
482 bool cgfs_remove(const char *controller, const char *cg)
483 {
484 size_t len;
485 char *dirnam, *tmpc = find_mounted_controller(controller);
486
487 if (!tmpc)
488 return false;
489 /* basedir / tmpc / cg \0 */
490 len = strlen(basedir) + strlen(tmpc) + strlen(cg) + 3;
491 dirnam = alloca(len);
492 snprintf(dirnam, len, "%s/%s/%s", basedir,tmpc, cg);
493 return recursive_rmdir(dirnam);
494 }
495
496 bool cgfs_chmod_file(const char *controller, const char *file, mode_t mode)
497 {
498 size_t len;
499 char *pathname, *tmpc = find_mounted_controller(controller);
500
501 if (!tmpc)
502 return false;
503 /* basedir / tmpc / file \0 */
504 len = strlen(basedir) + strlen(tmpc) + strlen(file) + 3;
505 pathname = alloca(len);
506 snprintf(pathname, len, "%s/%s/%s", basedir, tmpc, file);
507 if (chmod(pathname, mode) < 0)
508 return false;
509 return true;
510 }
511
512 static int chown_tasks_files(const char *dirname, uid_t uid, gid_t gid)
513 {
514 size_t len;
515 char *fname;
516
517 len = strlen(dirname) + strlen("/cgroup.procs") + 1;
518 fname = alloca(len);
519 snprintf(fname, len, "%s/tasks", dirname);
520 if (chown(fname, uid, gid) != 0)
521 return -errno;
522 snprintf(fname, len, "%s/cgroup.procs", dirname);
523 if (chown(fname, uid, gid) != 0)
524 return -errno;
525 return 0;
526 }
527
528 int cgfs_chown_file(const char *controller, const char *file, uid_t uid, gid_t gid)
529 {
530 size_t len;
531 char *pathname, *tmpc = find_mounted_controller(controller);
532
533 if (!tmpc)
534 return -EINVAL;
535 /* basedir / tmpc / file \0 */
536 len = strlen(basedir) + strlen(tmpc) + strlen(file) + 3;
537 pathname = alloca(len);
538 snprintf(pathname, len, "%s/%s/%s", basedir, tmpc, file);
539 if (chown(pathname, uid, gid) < 0)
540 return -errno;
541
542 if (is_dir(pathname))
543 // like cgmanager did, we want to chown the tasks file as well
544 return chown_tasks_files(pathname, uid, gid);
545
546 return 0;
547 }
548
549 FILE *open_pids_file(const char *controller, const char *cgroup)
550 {
551 size_t len;
552 char *pathname, *tmpc = find_mounted_controller(controller);
553
554 if (!tmpc)
555 return NULL;
556 /* basedir / tmpc / cgroup / "cgroup.procs" \0 */
557 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 4 + strlen("cgroup.procs");
558 pathname = alloca(len);
559 snprintf(pathname, len, "%s/%s/%s/cgroup.procs", basedir, tmpc, cgroup);
560 return fopen(pathname, "w");
561 }
562
563 bool cgfs_list_children(const char *controller, const char *cgroup, char ***list)
564 {
565 size_t len;
566 char *dirname, *tmpc = find_mounted_controller(controller);
567 char pathname[MAXPATHLEN];
568 size_t sz = 0, asz = BATCH_SIZE;
569 struct dirent dirent, *direntp;
570 DIR *dir;
571 int ret;
572
573 do {
574 *list = malloc(asz * sizeof(char *));
575 } while (!*list);
576 (*list)[0] = NULL;
577
578 if (!tmpc)
579 return NULL;
580
581 /* basedir / tmpc / cgroup \0 */
582 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
583 dirname = alloca(len);
584 snprintf(dirname, len, "%s/%s/%s", basedir, tmpc, cgroup);
585
586 dir = opendir(dirname);
587 if (!dir)
588 return false;
589
590 while (!readdir_r(dir, &dirent, &direntp)) {
591 struct stat mystat;
592 int rc;
593
594 if (!direntp)
595 break;
596
597 if (!strcmp(direntp->d_name, ".") ||
598 !strcmp(direntp->d_name, ".."))
599 continue;
600
601 rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
602 if (rc < 0 || rc >= MAXPATHLEN) {
603 fprintf(stderr, "%s: pathname too long under %s\n", __func__, dirname);
604 continue;
605 }
606
607 ret = lstat(pathname, &mystat);
608 if (ret) {
609 fprintf(stderr, "%s: failed to stat %s: %s\n", __func__, pathname, strerror(errno));
610 continue;
611 }
612 if (!S_ISDIR(mystat.st_mode))
613 continue;
614
615 if (sz+2 >= asz) {
616 char **tmp;
617 asz += BATCH_SIZE;
618 do {
619 tmp = realloc(*list, asz * sizeof(char *));
620 } while (!tmp);
621 *list = tmp;
622 }
623 do {
624 (*list)[sz] = strdup(direntp->d_name);
625 } while (!(*list)[sz]);
626 (*list)[sz+1] = NULL;
627 sz++;
628 }
629 if (closedir(dir) < 0) {
630 fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, dirname, strerror(errno));
631 return false;
632 }
633 return true;
634 }
635
636 void free_key(struct cgfs_files *k)
637 {
638 if (!k)
639 return;
640 free(k->name);
641 free(k);
642 }
643
644 void free_keys(struct cgfs_files **keys)
645 {
646 int i;
647
648 if (!keys)
649 return;
650 for (i = 0; keys[i]; i++) {
651 free_key(keys[i]);
652 }
653 free(keys);
654 }
655
656 bool cgfs_get_value(const char *controller, const char *cgroup, const char *file, char **value)
657 {
658 size_t len;
659 char *fnam, *tmpc = find_mounted_controller(controller);
660
661 if (!tmpc)
662 return false;
663 /* basedir / tmpc / cgroup / file \0 */
664 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
665 fnam = alloca(len);
666 snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, file);
667
668 *value = read_file(fnam);
669 return *value != NULL;
670 }
671
672 struct cgfs_files *cgfs_get_key(const char *controller, const char *cgroup, const char *file)
673 {
674 size_t len;
675 char *fnam, *tmpc = find_mounted_controller(controller);
676 struct stat sb;
677 struct cgfs_files *newkey;
678 int ret;
679
680 if (!tmpc)
681 return false;
682
683 if (file && *file == '/')
684 file++;
685
686 if (file && index(file, '/'))
687 return NULL;
688
689 /* basedir / tmpc / cgroup / file \0 */
690 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
691 if (file)
692 len += strlen(file) + 1;
693 fnam = alloca(len);
694 snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup,
695 file ? "/" : "", file ? file : "");
696
697 ret = stat(fnam, &sb);
698 if (ret < 0)
699 return NULL;
700
701 if (!S_ISREG(sb.st_mode))
702 return NULL;
703 do {
704 newkey = malloc(sizeof(struct cgfs_files));
705 } while (!newkey);
706 newkey->name = must_copy_string(file);
707 newkey->uid = sb.st_uid;
708 newkey->gid = sb.st_gid;
709 newkey->mode = sb.st_mode;
710
711 return newkey;
712 }
713
714 bool cgfs_list_keys(const char *controller, const char *cgroup, struct cgfs_files ***keys)
715 {
716 size_t len;
717 char *dirname, *tmpc = find_mounted_controller(controller);
718 char pathname[MAXPATHLEN];
719 size_t sz = 0, asz = 0;
720 struct dirent dirent, *direntp;
721 DIR *dir;
722 int ret;
723
724 *keys = NULL;
725 if (!tmpc)
726 return NULL;
727
728 /* basedir / tmpc / cgroup \0 */
729 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
730 dirname = alloca(len);
731 snprintf(dirname, len, "%s/%s/%s", basedir, tmpc, cgroup);
732
733 dir = opendir(dirname);
734 if (!dir)
735 return false;
736
737 while (!readdir_r(dir, &dirent, &direntp)) {
738 struct stat mystat;
739 int rc;
740
741 if (!direntp)
742 break;
743
744 if (!strcmp(direntp->d_name, ".") ||
745 !strcmp(direntp->d_name, ".."))
746 continue;
747
748 rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
749 if (rc < 0 || rc >= MAXPATHLEN) {
750 fprintf(stderr, "%s: pathname too long under %s\n", __func__, dirname);
751 continue;
752 }
753
754 ret = lstat(pathname, &mystat);
755 if (ret) {
756 fprintf(stderr, "%s: failed to stat %s: %s\n", __func__, pathname, strerror(errno));
757 continue;
758 }
759 if (!S_ISREG(mystat.st_mode))
760 continue;
761
762 if (sz+2 >= asz) {
763 struct cgfs_files **tmp;
764 asz += BATCH_SIZE;
765 do {
766 tmp = realloc(*keys, asz * sizeof(struct cgfs_files *));
767 } while (!tmp);
768 *keys = tmp;
769 }
770 (*keys)[sz] = cgfs_get_key(controller, cgroup, direntp->d_name);
771 (*keys)[sz+1] = NULL;
772 if (!(*keys)[sz]) {
773 fprintf(stderr, "%s: Error getting files under %s:%s\n",
774 __func__, controller, cgroup);
775 continue;
776 }
777 sz++;
778 }
779 if (closedir(dir) < 0) {
780 fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, dirname, strerror(errno));
781 return false;
782 }
783 return true;
784 }
785
786 bool is_child_cgroup(const char *controller, const char *cgroup, const char *f)
787 { size_t len;
788 char *fnam, *tmpc = find_mounted_controller(controller);
789 int ret;
790 struct stat sb;
791
792 if (!tmpc)
793 return false;
794 /* basedir / tmpc / cgroup / f \0 */
795 len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(f) + 4;
796 fnam = alloca(len);
797 snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, f);
798
799 ret = stat(fnam, &sb);
800 if (ret < 0 || !S_ISDIR(sb.st_mode))
801 return false;
802 return true;
803 }