cgroup_helpers.c 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // SPDX-License-Identifier: GPL-2.0
  2. #define _GNU_SOURCE
  3. #include <sched.h>
  4. #include <sys/mount.h>
  5. #include <sys/stat.h>
  6. #include <sys/types.h>
  7. #include <linux/limits.h>
  8. #include <stdio.h>
  9. #include <linux/sched.h>
  10. #include <fcntl.h>
  11. #include <unistd.h>
  12. #include <ftw.h>
  13. #include "cgroup_helpers.h"
  14. /*
  15. * To avoid relying on the system setup, when setup_cgroup_env is called
  16. * we create a new mount namespace, and cgroup namespace. The cgroup2
  17. * root is mounted at CGROUP_MOUNT_PATH
  18. *
  19. * Unfortunately, most people don't have cgroupv2 enabled at this point in time.
  20. * It's easier to create our own mount namespace and manage it ourselves.
  21. *
  22. * We assume /mnt exists.
  23. */
  24. #define WALK_FD_LIMIT 16
  25. #define CGROUP_MOUNT_PATH "/mnt"
  26. #define CGROUP_WORK_DIR "/cgroup-test-work-dir"
  27. #define format_cgroup_path(buf, path) \
  28. snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \
  29. CGROUP_WORK_DIR, path)
  30. /**
  31. * setup_cgroup_environment() - Setup the cgroup environment
  32. *
  33. * After calling this function, cleanup_cgroup_environment should be called
  34. * once testing is complete.
  35. *
  36. * This function will print an error to stderr and return 1 if it is unable
  37. * to setup the cgroup environment. If setup is successful, 0 is returned.
  38. */
  39. int setup_cgroup_environment(void)
  40. {
  41. char cgroup_workdir[PATH_MAX + 1];
  42. format_cgroup_path(cgroup_workdir, "");
  43. if (unshare(CLONE_NEWNS)) {
  44. log_err("unshare");
  45. return 1;
  46. }
  47. if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
  48. log_err("mount fakeroot");
  49. return 1;
  50. }
  51. if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) {
  52. log_err("mount cgroup2");
  53. return 1;
  54. }
  55. /* Cleanup existing failed runs, now that the environment is setup */
  56. cleanup_cgroup_environment();
  57. if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
  58. log_err("mkdir cgroup work dir");
  59. return 1;
  60. }
  61. return 0;
  62. }
  63. static int nftwfunc(const char *filename, const struct stat *statptr,
  64. int fileflags, struct FTW *pfwt)
  65. {
  66. if ((fileflags & FTW_D) && rmdir(filename))
  67. log_err("Removing cgroup: %s", filename);
  68. return 0;
  69. }
  70. static int join_cgroup_from_top(char *cgroup_path)
  71. {
  72. char cgroup_procs_path[PATH_MAX + 1];
  73. pid_t pid = getpid();
  74. int fd, rc = 0;
  75. snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
  76. "%s/cgroup.procs", cgroup_path);
  77. fd = open(cgroup_procs_path, O_WRONLY);
  78. if (fd < 0) {
  79. log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
  80. return 1;
  81. }
  82. if (dprintf(fd, "%d\n", pid) < 0) {
  83. log_err("Joining Cgroup");
  84. rc = 1;
  85. }
  86. close(fd);
  87. return rc;
  88. }
  89. /**
  90. * join_cgroup() - Join a cgroup
  91. * @path: The cgroup path, relative to the workdir, to join
  92. *
  93. * This function expects a cgroup to already be created, relative to the cgroup
  94. * work dir, and it joins it. For example, passing "/my-cgroup" as the path
  95. * would actually put the calling process into the cgroup
  96. * "/cgroup-test-work-dir/my-cgroup"
  97. *
  98. * On success, it returns 0, otherwise on failure it returns 1.
  99. */
  100. int join_cgroup(char *path)
  101. {
  102. char cgroup_path[PATH_MAX + 1];
  103. format_cgroup_path(cgroup_path, path);
  104. return join_cgroup_from_top(cgroup_path);
  105. }
  106. /**
  107. * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
  108. *
  109. * This is an idempotent function to delete all temporary cgroups that
  110. * have been created during the test, including the cgroup testing work
  111. * directory.
  112. *
  113. * At call time, it moves the calling process to the root cgroup, and then
  114. * runs the deletion process. It is idempotent, and should not fail, unless
  115. * a process is lingering.
  116. *
  117. * On failure, it will print an error to stderr, and try to continue.
  118. */
  119. void cleanup_cgroup_environment(void)
  120. {
  121. char cgroup_workdir[PATH_MAX + 1];
  122. format_cgroup_path(cgroup_workdir, "");
  123. join_cgroup_from_top(CGROUP_MOUNT_PATH);
  124. nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
  125. }
  126. /**
  127. * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
  128. * @path: The cgroup path, relative to the workdir, to join
  129. *
  130. * This function creates a cgroup under the top level workdir and returns the
  131. * file descriptor. It is idempotent.
  132. *
  133. * On success, it returns the file descriptor. On failure it returns 0.
  134. * If there is a failure, it prints the error to stderr.
  135. */
  136. int create_and_get_cgroup(char *path)
  137. {
  138. char cgroup_path[PATH_MAX + 1];
  139. int fd;
  140. format_cgroup_path(cgroup_path, path);
  141. if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
  142. log_err("mkdiring cgroup %s .. %s", path, cgroup_path);
  143. return 0;
  144. }
  145. fd = open(cgroup_path, O_RDONLY);
  146. if (fd < 0) {
  147. log_err("Opening Cgroup");
  148. return 0;
  149. }
  150. return fd;
  151. }