]> git.proxmox.com Git - mirror_lxcfs.git/blob - src/cgroups/cgroup_utils.c
Merge pull request #473 from brauner/2021-08-31.fixes
[mirror_lxcfs.git] / src / cgroups / cgroup_utils.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE
5 #endif
6
7 #include "../config.h"
8
9 #ifdef HAVE_FUSE3
10 #ifndef FUSE_USE_VERSION
11 #define FUSE_USE_VERSION 30
12 #endif
13 #else
14 #ifndef FUSE_USE_VERSION
15 #define FUSE_USE_VERSION 26
16 #endif
17 #endif
18
19 #define _FILE_OFFSET_BITS 64
20
21 #include <fcntl.h>
22 #include <stdarg.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/mount.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <sys/vfs.h>
31 #include <unistd.h>
32
33 #include "../macro.h"
34 #include "../memory_utils.h"
35 #include "cgroup.h"
36 #include "cgroup_utils.h"
37
38 int get_cgroup_version(char *line)
39 {
40 if (is_cgroupfs_v1(line))
41 return CGROUP_SUPER_MAGIC;
42
43 if (is_cgroupfs_v2(line))
44 return CGROUP2_SUPER_MAGIC;
45
46 return 0;
47 }
48
49 bool is_cgroupfs_v1(char *line)
50 {
51 char *p = strstr(line, " - ");
52 if (!p)
53 return false;
54 return strncmp(p, " - cgroup ", 10) == 0;
55 }
56
57 bool is_cgroupfs_v2(char *line)
58 {
59 char *p = strstr(line, " - ");
60 if (!p)
61 return false;
62
63 return strncmp(p, " - cgroup2 ", 11) == 0;
64 }
65
66 int unified_cgroup_hierarchy(void)
67 {
68
69 int ret;
70 struct statfs fs;
71
72 ret = statfs(DEFAULT_CGROUP_MOUNTPOINT, &fs);
73 if (ret < 0)
74 return -ENOMEDIUM;
75
76 if (is_fs_type(&fs, CGROUP2_SUPER_MAGIC))
77 return CGROUP2_SUPER_MAGIC;
78
79 return 0;
80 }
81
82 bool is_cgroup_fd(int fd)
83 {
84
85 int ret;
86 struct statfs fs;
87
88 ret = fstatfs(fd, &fs);
89 if (ret)
90 return false;
91
92 if (is_fs_type(&fs, CGROUP2_SUPER_MAGIC) ||
93 is_fs_type(&fs, CGROUP_SUPER_MAGIC))
94 return true;
95
96 return false;
97 }
98
99 void *must_realloc(void *orig, size_t sz)
100 {
101 void *ret;
102
103 do {
104 ret = realloc(orig, sz);
105 } while (!ret);
106
107 return ret;
108 }
109
110 char *must_make_path(const char *first, ...)
111 {
112 va_list args;
113 char *cur, *dest;
114 size_t full_len = strlen(first);
115 size_t buf_len;
116 size_t cur_len;
117
118 dest = must_copy_string(first);
119 cur_len = full_len;
120
121 va_start(args, first);
122 while ((cur = va_arg(args, char *)) != NULL) {
123 buf_len = strlen(cur);
124
125 full_len += buf_len;
126 if (cur[0] != '/')
127 full_len++;
128
129 dest = must_realloc(dest, full_len + 1);
130
131 if (cur[0] != '/') {
132 memcpy(dest + cur_len, "/", 1);
133 cur_len++;
134 }
135
136 memcpy(dest + cur_len, cur, buf_len);
137 cur_len += buf_len;
138 }
139 va_end(args);
140
141 dest[cur_len] = '\0';
142 return dest;
143 }
144
145 bool is_fs_type(const struct statfs *fs, fs_type_magic magic_val)
146 {
147 return (fs->f_type == (fs_type_magic)magic_val);
148 }
149
150 char *must_copy_string(const char *entry)
151 {
152 char *ret;
153
154 if (!entry)
155 return NULL;
156
157 do {
158 ret = strdup(entry);
159 } while (!ret);
160
161 return ret;
162 }
163
164 char *lxc_string_join(const char *sep, const char **parts, bool use_as_prefix)
165 {
166 char *result;
167 char **p;
168 size_t sep_len = strlen(sep);
169 size_t result_len = use_as_prefix * sep_len;
170 size_t buf_len;
171
172 /* calculate new string length */
173 for (p = (char **)parts; *p; p++)
174 result_len += (p > (char **)parts) * sep_len + strlen(*p);
175
176 buf_len = result_len + 1;
177 result = calloc(buf_len, 1);
178 if (!result)
179 return NULL;
180
181 if (use_as_prefix)
182 (void)strlcpy(result, sep, buf_len);
183
184 for (p = (char **)parts; *p; p++) {
185 if (p > (char **)parts)
186 (void)strlcat(result, sep, buf_len);
187
188 (void)strlcat(result, *p, buf_len);
189 }
190
191 return result;
192 }
193
194 int lxc_count_file_lines(const char *fn)
195 {
196 __do_fclose FILE *f = NULL;
197 __do_free char *line = NULL;
198 size_t sz = 0;
199 int n = 0;
200
201 f = fopen_cloexec(fn, "r");
202 if (!f)
203 return -1;
204
205 while (getline(&line, &sz, f) != -1)
206 n++;
207
208 return n;
209 }
210
211 bool dir_exists(const char *path)
212 {
213 struct stat sb;
214 int ret;
215
216 ret = stat(path, &sb);
217 if (ret < 0)
218 /* Could be something other than eexist, just say "no". */
219 return false;
220
221 return S_ISDIR(sb.st_mode);
222 }
223
224 /*
225 * @path: a pathname where / replaced with '\0'.
226 * @offsetp: pointer to int showing which path segment was last seen.
227 * Updated on return to reflect the next segment.
228 * @fulllen: full original path length.
229 * Returns a pointer to the next path segment, or NULL if done.
230 */
231 static char *get_nextpath(char *path, int *offsetp, int fulllen)
232 {
233 int offset = *offsetp;
234
235 if (offset >= fulllen)
236 return NULL;
237
238 while (offset < fulllen && path[offset] != '\0')
239 offset++;
240
241 while (offset < fulllen && path[offset] == '\0')
242 offset++;
243
244 *offsetp = offset;
245
246 return (offset < fulllen) ? &path[offset] : NULL;
247 }
248
249 /*
250 * Check that @subdir is a subdir of @dir. @len is the length of
251 * @dir (to avoid having to recalculate it).
252 */
253 static bool is_subdir(const char *subdir, const char *dir, size_t len)
254 {
255 size_t subdirlen = strlen(subdir);
256
257 if (subdirlen < len)
258 return false;
259
260 if (strncmp(subdir, dir, len) != 0)
261 return false;
262
263 if (dir[len-1] == '/')
264 return true;
265
266 if (subdir[len] == '/' || subdirlen == len)
267 return true;
268
269 return false;
270 }
271
272 /*
273 * Check if the open fd is a symlink. Return -ELOOP if it is. Return
274 * -ENOENT if we couldn't fstat. Return 0 if the fd is ok.
275 */
276 static int check_symlink(int fd)
277 {
278 struct stat sb;
279 int ret;
280
281 ret = fstat(fd, &sb);
282 if (ret < 0)
283 return -ENOENT;
284
285 if (S_ISLNK(sb.st_mode))
286 return -ELOOP;
287
288 return 0;
289 }
290
291 /*
292 * Open a file or directory, provided that it contains no symlinks.
293 *
294 * CAVEAT: This function must not be used for other purposes than container
295 * setup before executing the container's init
296 */
297 static int open_if_safe(int dirfd, const char *nextpath)
298 {
299 __do_close int newfd = -EBADF;
300
301 newfd = openat(dirfd, nextpath, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
302 if (newfd >= 0) /* Was not a symlink, all good. */
303 return move_fd(newfd);
304
305 if (errno == ELOOP)
306 return -1;
307
308 if (errno == EPERM || errno == EACCES) {
309 /* We're not root (cause we got EPERM) so try opening with
310 * O_PATH.
311 */
312 newfd = openat(dirfd, nextpath, O_PATH | O_NOFOLLOW);
313 if (newfd >= 0) {
314 /* O_PATH will return an fd for symlinks. We know
315 * nextpath wasn't a symlink at last openat, so if fd is
316 * now a link, then something * fishy is going on.
317 */
318 int ret = check_symlink(newfd);
319 if (ret < 0)
320 return -1;
321 }
322 }
323
324 return move_fd(newfd);
325 }
326
327 /*
328 * Open a path intending for mounting, ensuring that the final path
329 * is inside the container's rootfs.
330 *
331 * CAVEAT: This function must not be used for other purposes than container
332 * setup before executing the container's init
333 *
334 * @target: path to be opened
335 * @prefix_skip: a part of @target in which to ignore symbolic links. This
336 * would be the container's rootfs.
337 *
338 * Return an open fd for the path, or <0 on error.
339 */
340 static int open_without_symlink(const char *target, const char *prefix_skip)
341 {
342 __do_close int dirfd = -EBADF;
343 __do_free char *dup = NULL;
344 int curlen = 0, fulllen, i;
345
346 fulllen = strlen(target);
347
348 /* make sure prefix-skip makes sense */
349 if (prefix_skip && strlen(prefix_skip) > 0) {
350 curlen = strlen(prefix_skip);
351 if (!is_subdir(target, prefix_skip, curlen))
352 return -EINVAL;
353
354 /*
355 * get_nextpath() expects the curlen argument to be
356 * on a (turned into \0) / or before it, so decrement
357 * curlen to make sure that happens
358 */
359 if (curlen)
360 curlen--;
361 } else {
362 prefix_skip = "/";
363 curlen = 0;
364 }
365
366 /* Make a copy of target which we can hack up, and tokenize it */
367 dup = strdup(target);
368 if (!dup)
369 return ret_errno(ENOMEM);
370
371 for (i = 0; i < fulllen; i++) {
372 if (dup[i] == '/')
373 dup[i] = '\0';
374 }
375
376 dirfd = open(prefix_skip, O_RDONLY);
377 if (dirfd < 0)
378 return -1;
379
380 for (;;) {
381 int newfd;
382 char *nextpath;
383
384 nextpath = get_nextpath(dup, &curlen, fulllen);
385 if (!nextpath)
386 return move_fd(dirfd);
387
388 newfd = open_if_safe(dirfd, nextpath);
389 close_prot_errno_disarm(dirfd);
390 dirfd = newfd;
391 if (newfd < 0)
392 return -1;
393 }
394
395 return move_fd(dirfd);
396 }
397
398 /*
399 * Safely mount a path into a container, ensuring that the mount target
400 * is under the container's @rootfs. (If @rootfs is NULL, then the container
401 * uses the host's /)
402 *
403 * CAVEAT: This function must not be used for other purposes than container
404 * setup before executing the container's init
405 */
406 int safe_mount(const char *src, const char *dest, const char *fstype,
407 unsigned long flags, const void *data, const char *rootfs)
408 {
409 __do_close int destfd = -EBADF, srcfd = -EBADF;
410 int ret;
411 /* Only needs enough for /proc/self/fd/<fd>. */
412 char srcbuf[50], destbuf[50];
413 const char *mntsrc = src;
414
415 if (!rootfs)
416 rootfs = "";
417
418 /* todo - allow symlinks for relative paths if 'allowsymlinks' option is passed */
419 if (flags & MS_BIND && src && src[0] != '/') {
420
421 srcfd = open_without_symlink(src, NULL);
422 if (srcfd < 0)
423 return srcfd;
424
425 ret = snprintf(srcbuf, sizeof(srcbuf), "/proc/self/fd/%d", srcfd);
426 if (ret < 0 || ret >= (int)sizeof(srcbuf))
427 return -EINVAL;
428 mntsrc = srcbuf;
429 }
430
431 destfd = open_without_symlink(dest, rootfs);
432 if (destfd < 0)
433 return -1;
434
435 ret = snprintf(destbuf, sizeof(destbuf), "/proc/self/fd/%d", destfd);
436 if (ret < 0 || ret >= (int)sizeof(destbuf))
437 return ret_errno(EINVAL);
438
439 ret = mount(mntsrc, destbuf, fstype, flags, data);
440 if (ret < 0)
441 return -1;
442
443 return 0;
444 }
445
446 #ifndef HAVE_STRLCPY
447 size_t strlcpy(char *dest, const char *src, size_t size)
448 {
449 size_t ret = strlen(src);
450
451 if (size) {
452 size_t len = (ret >= size) ? size - 1 : ret;
453 memcpy(dest, src, len);
454 dest[len] = '\0';
455 }
456
457 return ret;
458 }
459 #endif
460
461 #ifndef HAVE_STRLCAT
462 size_t strlcat(char *d, const char *s, size_t n)
463 {
464 size_t l = strnlen(d, n);
465 if (l == n)
466 return l + strlen(s);
467
468 return l + strlcpy(d + l, s, n - l);
469 }
470 #endif
471
472 FILE *fopen_cloexec(const char *path, const char *mode)
473 {
474 __do_close int fd = -EBADF;
475 __do_fclose FILE *ret = NULL;
476 int open_mode = 0;
477 int step = 0;
478
479 if (!strncmp(mode, "r+", 2)) {
480 open_mode = O_RDWR;
481 step = 2;
482 } else if (!strncmp(mode, "r", 1)) {
483 open_mode = O_RDONLY;
484 step = 1;
485 } else if (!strncmp(mode, "w+", 2)) {
486 open_mode = O_RDWR | O_TRUNC | O_CREAT;
487 step = 2;
488 } else if (!strncmp(mode, "w", 1)) {
489 open_mode = O_WRONLY | O_TRUNC | O_CREAT;
490 step = 1;
491 } else if (!strncmp(mode, "a+", 2)) {
492 open_mode = O_RDWR | O_CREAT | O_APPEND;
493 step = 2;
494 } else if (!strncmp(mode, "a", 1)) {
495 open_mode = O_WRONLY | O_CREAT | O_APPEND;
496 step = 1;
497 }
498 for (; mode[step]; step++)
499 if (mode[step] == 'x')
500 open_mode |= O_EXCL;
501 open_mode |= O_CLOEXEC;
502
503 fd = open(path, open_mode, 0660);
504 if (fd < 0)
505 return NULL;
506
507 ret = fdopen(fd, mode);
508 if (!ret)
509 return NULL;
510 move_fd(fd);
511
512 return move_ptr(ret);
513 }
514
515 /* Given a multi-line string, return a null-terminated copy of the current line. */
516 static char *copy_to_eol(char *p)
517 {
518 char *p2 = strchr(p, '\n'), *sret;
519 size_t len;
520
521 if (!p2)
522 return NULL;
523
524 len = p2 - p;
525 sret = must_realloc(NULL, len + 1);
526 memcpy(sret, p, len);
527 sret[len] = '\0';
528 return sret;
529 }
530
531 static void batch_realloc(char **mem, size_t oldlen, size_t newlen)
532 {
533 int newbatches = (newlen / BATCH_SIZE) + 1;
534 int oldbatches = (oldlen / BATCH_SIZE) + 1;
535
536 if (!*mem || newbatches > oldbatches) {
537 *mem = must_realloc(*mem, newbatches * BATCH_SIZE);
538 }
539 }
540
541 void append_line(char **dest, size_t oldlen, char *new, size_t newlen)
542 {
543 size_t full = oldlen + newlen;
544
545 batch_realloc(dest, oldlen, full + 1);
546
547 memcpy(*dest + oldlen, new, newlen + 1);
548 }
549
550 static inline void drop_trailing_newlines(char *s)
551 {
552 int l;
553
554 for (l = strlen(s); l > 0 && s[l - 1] == '\n'; l--)
555 s[l - 1] = '\0';
556 }
557
558 /* Slurp in a whole file */
559 char *read_file(const char *fnam)
560 {
561 __do_free char *line = NULL;
562 __do_fclose FILE *f = NULL;
563 int linelen;
564 char *buf = NULL;
565 size_t len = 0, fulllen = 0;
566
567 f = fopen(fnam, "re");
568 if (!f)
569 return NULL;
570 while ((linelen = getline(&line, &len, f)) != -1) {
571 append_line(&buf, fulllen, line, linelen);
572 fulllen += linelen;
573 }
574 return buf;
575 }
576
577 char *read_file_strip_newline(const char *fnam)
578 {
579 char *buf;
580
581 buf = read_file(fnam);
582 if (buf)
583 drop_trailing_newlines(buf);
584 return buf;
585 }
586
587 /* Get current cgroup from /proc/self/cgroup for the cgroupfs v2 hierarchy. */
588 char *cg_unified_get_current_cgroup(pid_t pid)
589 {
590 __do_free char *basecginfo = NULL;
591 char path[STRLITERALLEN("/proc//cgroup") + INTTYPE_TO_STRLEN(pid_t) + 1];
592 char *base_cgroup;
593
594 snprintf(path, sizeof(path), "/proc/%d/cgroup", pid > 0 ? pid : 1);
595 basecginfo = read_file(path);
596 if (!basecginfo)
597 return NULL;
598
599 base_cgroup = strstr(basecginfo, "0::/");
600 if (!base_cgroup)
601 return NULL;
602
603 base_cgroup = base_cgroup + 3;
604 return copy_to_eol(base_cgroup);
605 }
606
607 /* cgline: pointer to character after the first ':' in a line in a \n-terminated
608 * /proc/self/cgroup file. Check whether controller c is present.
609 */
610 static bool controller_in_clist(char *cgline, const char *c)
611 {
612 __do_free char *tmp = NULL;
613 char *tok, *eol;
614 size_t len;
615
616 eol = strchr(cgline, ':');
617 if (!eol)
618 return false;
619
620 len = eol - cgline;
621 tmp = must_realloc(NULL, len + 1);
622 memcpy(tmp, cgline, len);
623 tmp[len] = '\0';
624
625 lxc_iterate_parts(tok, tmp, ",")
626 if (strcmp(tok, c) == 0)
627 return true;
628
629 return false;
630 }
631
632 /* @basecginfo is a copy of /proc/$$/cgroup. Return the current cgroup for
633 * @controller.
634 */
635 char *cg_hybrid_get_current_cgroup(char *basecginfo, const char *controller, int type)
636 {
637 char *p = basecginfo;
638
639 for (;;) {
640 bool is_cgv2_base_cgroup = false;
641
642 /* cgroup v2 entry in "/proc/<pid>/cgroup": "0::/some/path" */
643 if ((type == CGROUP2_SUPER_MAGIC) && (*p == '0'))
644 is_cgv2_base_cgroup = true;
645
646 p = strchr(p, ':');
647 if (!p)
648 return NULL;
649 p++;
650
651 if (is_cgv2_base_cgroup || (controller && controller_in_clist(p, controller))) {
652 p = strchr(p, ':');
653 if (!p)
654 return NULL;
655 p++;
656 return copy_to_eol(p);
657 }
658
659 p = strchr(p, '\n');
660 if (!p)
661 return NULL;
662 p++;
663 }
664 }
665
666 char *cg_legacy_get_current_cgroup(pid_t pid, const char *controller)
667 {
668 __do_free char *basecginfo = NULL;
669 char path[STRLITERALLEN("/proc//cgroup") + INTTYPE_TO_STRLEN(pid_t) + 1];
670
671 snprintf(path, sizeof(path), "/proc/%d/cgroup", pid > 0 ? pid : 1);
672 basecginfo = read_file(path);
673 if (!basecginfo)
674 return ret_set_errno(NULL, ENOMEM);
675
676 return cg_hybrid_get_current_cgroup(basecginfo, controller,
677 CGROUP_SUPER_MAGIC);
678 }
679
680
681 char *readat_file(int dirfd, const char *path)
682 {
683 __do_close int fd = -EBADF;
684 __do_free char *line = NULL;
685 __do_fclose FILE *f = NULL;
686 char *buf = NULL;
687 size_t len = 0, fulllen = 0;
688 ssize_t linelen;
689
690 fd = openat(dirfd, path, O_NOFOLLOW | O_RDONLY | O_CLOEXEC);
691 if (fd < 0)
692 return NULL;
693
694 f = fdopen(fd, "re");
695 if (!f)
696 return NULL;
697 /* Transfer ownership of fd */
698 move_fd(fd);
699
700 while ((linelen = getline(&line, &len, f)) != -1) {
701 append_line(&buf, fulllen, line, linelen);
702 fulllen += linelen;
703 }
704
705 if (buf)
706 drop_trailing_newlines(buf);
707
708 return buf;
709 }
710
711 bool mkdir_p(const char *dir, mode_t mode)
712 {
713 const char *tmp = dir;
714 const char *orig = dir;
715 char *makeme;
716
717 do {
718 dir = tmp + strspn(tmp, "/");
719 tmp = dir + strcspn(dir, "/");
720 makeme = strndup(orig, dir - orig);
721 if (!makeme)
722 return false;
723 if (mkdir(makeme, mode) && errno != EEXIST) {
724 lxcfs_error("Failed to create directory '%s': %s.\n",
725 makeme, strerror(errno));
726 free(makeme);
727 return false;
728 }
729 free(makeme);
730 } while(tmp != dir);
731
732 return true;
733 }
734
735 static bool same_file(int fd1, int fd2)
736 {
737 struct stat st1, st2;
738
739 if (fstat(fd1, &st1) < 0 || fstat(fd2, &st2) < 0)
740 return false;
741
742 return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino);
743 }
744
745 /**
746 * cgroup_walkup_to_root() - Walk upwards to cgroup root to find valid value
747 *
748 * @cgroup2_root_fd: File descriptor for the cgroup2 root mount point.
749 * @hierarchy_fd: File descriptor for the hierarchy.
750 * @cgroup: A cgroup directory relative to @hierarchy_fd.
751 * @file: The file in @cgroup from which to read a value.
752 * @value: Return argument to store value read from @file.
753 *
754 * This function tries to read a valid value from @file in @cgroup in
755 * @hierarchy_fd. If it is a legacy cgroup hierarchy and we fail to find a
756 * valid value we terminate early and report an error.
757 * The cgroup2 hierarchy however, has different semantics. In a few controller
758 * files it will show the value "max" or simply leave it completely empty
759 * thereby indicating that no limit has been set for this particular cgroup.
760 * However, that doesn't mean that there's no limit. A cgroup further up the
761 * hierarchy could have a limit set that also applies to the cgroup we are
762 * interested in. So for the unified cgroup hierarchy we need to keep walking
763 * towards the cgroup2 root cgroup and try to parse a valid value.
764 *
765 * Returns: 0 if a limit was found, 1 if no limit was set or "max" was set,
766 * -errno if an error occurred.
767 */
768 int cgroup_walkup_to_root(int cgroup2_root_fd, int hierarchy_fd,
769 const char *cgroup, const char *file, char **value)
770 {
771 __do_close int dir_fd = -EBADF;
772 __do_free char *val = NULL;
773
774 /* Look in our current cgroup for a valid value. */
775 dir_fd = openat(hierarchy_fd, cgroup, O_DIRECTORY | O_PATH | O_CLOEXEC);
776 if (dir_fd < 0)
777 return -errno;
778
779 val = readat_file(dir_fd, file);
780 if (!is_empty_string(val) && strcmp(val, "max") != 0) {
781 *value = move_ptr(val);
782 return 0;
783 }
784
785 /*
786 * Legacy cgroup hierarchies should always show a valid value in the
787 * file of the cgroup. So no need to do this upwards walking crap.
788 */
789 if (cgroup2_root_fd < 0)
790 return -EINVAL;
791 else if (same_file(cgroup2_root_fd, dir_fd))
792 return 1;
793
794 free_disarm(val);
795 /*
796 * Set an arbitraty hard-coded limit to prevent us from ending
797 * up in an endless loop. There really shouldn't be any cgroup
798 * tree that is 1000 levels deep. That would be insane in
799 * principal and performance-wise.
800 */
801 for (int i = 0; i < 1000; i++) {
802 __do_close int inner_fd = -EBADF;
803 __do_free char *new_val = NULL;
804
805 inner_fd = move_fd(dir_fd);
806 dir_fd = openat(inner_fd, "..", O_DIRECTORY | O_PATH | O_CLOEXEC);
807 if (dir_fd < 0)
808 return -errno;
809
810 /*
811 * We're at the root of the cgroup2 tree so stop walking
812 * upwards.
813 * Since we walked up the whole tree we haven't found an actual
814 * limit anywhere apparently.
815 *
816 * Note that we're not checking the root cgroup itself simply
817 * because a lot of the controllers don't expose files with
818 * limits to the root cgroup.
819 */
820 if (same_file(cgroup2_root_fd, dir_fd))
821 return 1;
822
823 /* We found a valid value. Terminate walk. */
824 new_val = readat_file(dir_fd, file);
825 if (!is_empty_string(new_val) && strcmp(new_val, "max") != 0) {
826 *value = move_ptr(new_val);
827 return 0;
828 }
829 }
830
831 return log_error_errno(-ELOOP, ELOOP, "To many nested cgroups or invalid mount tree. Terminating walk");
832 }