cgroup_helpers.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  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 <stdlib.h>
  10. #include <linux/sched.h>
  11. #include <fcntl.h>
  12. #include <unistd.h>
  13. #include <ftw.h>
  14. #include "cgroup_helpers.h"
  15. /*
  16. * To avoid relying on the system setup, when setup_cgroup_env is called
  17. * we create a new mount namespace, and cgroup namespace. The cgroupv2
  18. * root is mounted at CGROUP_MOUNT_PATH. Unfortunately, most people don't
  19. * have cgroupv2 enabled at this point in time. It's easier to create our
  20. * own mount namespace and manage it ourselves. We assume /mnt exists.
  21. *
  22. * Related cgroupv1 helpers are named *classid*(), since we only use the
  23. * net_cls controller for tagging net_cls.classid. We assume the default
  24. * mount under /sys/fs/cgroup/net_cls, which should be the case for the
  25. * vast majority of users.
  26. */
  27. #define WALK_FD_LIMIT 16
  28. #define CGROUP_MOUNT_PATH "/mnt"
  29. #define CGROUP_MOUNT_DFLT "/sys/fs/cgroup"
  30. #define NETCLS_MOUNT_PATH CGROUP_MOUNT_DFLT "/net_cls"
  31. #define CGROUP_WORK_DIR "/cgroup-test-work-dir"
  32. #define format_cgroup_path_pid(buf, path, pid) \
  33. snprintf(buf, sizeof(buf), "%s%s%d%s", CGROUP_MOUNT_PATH, \
  34. CGROUP_WORK_DIR, pid, path)
  35. #define format_cgroup_path(buf, path) \
  36. format_cgroup_path_pid(buf, path, getpid())
  37. #define format_parent_cgroup_path(buf, path) \
  38. format_cgroup_path_pid(buf, path, getppid())
  39. #define format_classid_path(buf) \
  40. snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH, \
  41. CGROUP_WORK_DIR)
  42. static int __enable_controllers(const char *cgroup_path, const char *controllers)
  43. {
  44. char path[PATH_MAX + 1];
  45. char enable[PATH_MAX + 1];
  46. char *c, *c2;
  47. int fd, cfd;
  48. ssize_t len;
  49. /* If not controllers are passed, enable all available controllers */
  50. if (!controllers) {
  51. snprintf(path, sizeof(path), "%s/cgroup.controllers",
  52. cgroup_path);
  53. fd = open(path, O_RDONLY);
  54. if (fd < 0) {
  55. log_err("Opening cgroup.controllers: %s", path);
  56. return 1;
  57. }
  58. len = read(fd, enable, sizeof(enable) - 1);
  59. if (len < 0) {
  60. close(fd);
  61. log_err("Reading cgroup.controllers: %s", path);
  62. return 1;
  63. } else if (len == 0) { /* No controllers to enable */
  64. close(fd);
  65. return 0;
  66. }
  67. enable[len] = 0;
  68. close(fd);
  69. } else {
  70. strncpy(enable, controllers, sizeof(enable));
  71. }
  72. snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path);
  73. cfd = open(path, O_RDWR);
  74. if (cfd < 0) {
  75. log_err("Opening cgroup.subtree_control: %s", path);
  76. return 1;
  77. }
  78. for (c = strtok_r(enable, " ", &c2); c; c = strtok_r(NULL, " ", &c2)) {
  79. if (dprintf(cfd, "+%s\n", c) <= 0) {
  80. log_err("Enabling controller %s: %s", c, path);
  81. close(cfd);
  82. return 1;
  83. }
  84. }
  85. close(cfd);
  86. return 0;
  87. }
  88. /**
  89. * enable_controllers() - Enable cgroup v2 controllers
  90. * @relative_path: The cgroup path, relative to the workdir
  91. * @controllers: List of controllers to enable in cgroup.controllers format
  92. *
  93. *
  94. * Enable given cgroup v2 controllers, if @controllers is NULL, enable all
  95. * available controllers.
  96. *
  97. * If successful, 0 is returned.
  98. */
  99. int enable_controllers(const char *relative_path, const char *controllers)
  100. {
  101. char cgroup_path[PATH_MAX + 1];
  102. format_cgroup_path(cgroup_path, relative_path);
  103. return __enable_controllers(cgroup_path, controllers);
  104. }
  105. static int __write_cgroup_file(const char *cgroup_path, const char *file,
  106. const char *buf)
  107. {
  108. char file_path[PATH_MAX + 1];
  109. int fd;
  110. snprintf(file_path, sizeof(file_path), "%s/%s", cgroup_path, file);
  111. fd = open(file_path, O_RDWR);
  112. if (fd < 0) {
  113. log_err("Opening %s", file_path);
  114. return 1;
  115. }
  116. if (dprintf(fd, "%s", buf) <= 0) {
  117. log_err("Writing to %s", file_path);
  118. close(fd);
  119. return 1;
  120. }
  121. close(fd);
  122. return 0;
  123. }
  124. /**
  125. * write_cgroup_file() - Write to a cgroup file
  126. * @relative_path: The cgroup path, relative to the workdir
  127. * @file: The name of the file in cgroupfs to write to
  128. * @buf: Buffer to write to the file
  129. *
  130. * Write to a file in the given cgroup's directory.
  131. *
  132. * If successful, 0 is returned.
  133. */
  134. int write_cgroup_file(const char *relative_path, const char *file,
  135. const char *buf)
  136. {
  137. char cgroup_path[PATH_MAX - 24];
  138. format_cgroup_path(cgroup_path, relative_path);
  139. return __write_cgroup_file(cgroup_path, file, buf);
  140. }
  141. /**
  142. * write_cgroup_file_parent() - Write to a cgroup file in the parent process
  143. * workdir
  144. * @relative_path: The cgroup path, relative to the parent process workdir
  145. * @file: The name of the file in cgroupfs to write to
  146. * @buf: Buffer to write to the file
  147. *
  148. * Write to a file in the given cgroup's directory under the parent process
  149. * workdir.
  150. *
  151. * If successful, 0 is returned.
  152. */
  153. int write_cgroup_file_parent(const char *relative_path, const char *file,
  154. const char *buf)
  155. {
  156. char cgroup_path[PATH_MAX - 24];
  157. format_parent_cgroup_path(cgroup_path, relative_path);
  158. return __write_cgroup_file(cgroup_path, file, buf);
  159. }
  160. /**
  161. * setup_cgroup_environment() - Setup the cgroup environment
  162. *
  163. * After calling this function, cleanup_cgroup_environment should be called
  164. * once testing is complete.
  165. *
  166. * This function will print an error to stderr and return 1 if it is unable
  167. * to setup the cgroup environment. If setup is successful, 0 is returned.
  168. */
  169. int setup_cgroup_environment(void)
  170. {
  171. char cgroup_workdir[PATH_MAX - 24];
  172. format_cgroup_path(cgroup_workdir, "");
  173. if (unshare(CLONE_NEWNS)) {
  174. log_err("unshare");
  175. return 1;
  176. }
  177. if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
  178. log_err("mount fakeroot");
  179. return 1;
  180. }
  181. if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) {
  182. log_err("mount cgroup2");
  183. return 1;
  184. }
  185. /* Cleanup existing failed runs, now that the environment is setup */
  186. cleanup_cgroup_environment();
  187. if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
  188. log_err("mkdir cgroup work dir");
  189. return 1;
  190. }
  191. /* Enable all available controllers to increase test coverage */
  192. if (__enable_controllers(CGROUP_MOUNT_PATH, NULL) ||
  193. __enable_controllers(cgroup_workdir, NULL))
  194. return 1;
  195. return 0;
  196. }
  197. static int nftwfunc(const char *filename, const struct stat *statptr,
  198. int fileflags, struct FTW *pfwt)
  199. {
  200. if ((fileflags & FTW_D) && rmdir(filename))
  201. log_err("Removing cgroup: %s", filename);
  202. return 0;
  203. }
  204. static int join_cgroup_from_top(const char *cgroup_path)
  205. {
  206. char cgroup_procs_path[PATH_MAX + 1];
  207. pid_t pid = getpid();
  208. int fd, rc = 0;
  209. snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
  210. "%s/cgroup.procs", cgroup_path);
  211. fd = open(cgroup_procs_path, O_WRONLY);
  212. if (fd < 0) {
  213. log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
  214. return 1;
  215. }
  216. if (dprintf(fd, "%d\n", pid) < 0) {
  217. log_err("Joining Cgroup");
  218. rc = 1;
  219. }
  220. close(fd);
  221. return rc;
  222. }
  223. /**
  224. * join_cgroup() - Join a cgroup
  225. * @relative_path: The cgroup path, relative to the workdir, to join
  226. *
  227. * This function expects a cgroup to already be created, relative to the cgroup
  228. * work dir, and it joins it. For example, passing "/my-cgroup" as the path
  229. * would actually put the calling process into the cgroup
  230. * "/cgroup-test-work-dir/my-cgroup"
  231. *
  232. * On success, it returns 0, otherwise on failure it returns 1.
  233. */
  234. int join_cgroup(const char *relative_path)
  235. {
  236. char cgroup_path[PATH_MAX + 1];
  237. format_cgroup_path(cgroup_path, relative_path);
  238. return join_cgroup_from_top(cgroup_path);
  239. }
  240. /**
  241. * join_parent_cgroup() - Join a cgroup in the parent process workdir
  242. * @relative_path: The cgroup path, relative to parent process workdir, to join
  243. *
  244. * See join_cgroup().
  245. *
  246. * On success, it returns 0, otherwise on failure it returns 1.
  247. */
  248. int join_parent_cgroup(const char *relative_path)
  249. {
  250. char cgroup_path[PATH_MAX + 1];
  251. format_parent_cgroup_path(cgroup_path, relative_path);
  252. return join_cgroup_from_top(cgroup_path);
  253. }
  254. /**
  255. * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
  256. *
  257. * This is an idempotent function to delete all temporary cgroups that
  258. * have been created during the test, including the cgroup testing work
  259. * directory.
  260. *
  261. * At call time, it moves the calling process to the root cgroup, and then
  262. * runs the deletion process. It is idempotent, and should not fail, unless
  263. * a process is lingering.
  264. *
  265. * On failure, it will print an error to stderr, and try to continue.
  266. */
  267. void cleanup_cgroup_environment(void)
  268. {
  269. char cgroup_workdir[PATH_MAX + 1];
  270. format_cgroup_path(cgroup_workdir, "");
  271. join_cgroup_from_top(CGROUP_MOUNT_PATH);
  272. nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
  273. }
  274. /**
  275. * get_root_cgroup() - Get the FD of the root cgroup
  276. *
  277. * On success, it returns the file descriptor. On failure, it returns -1.
  278. * If there is a failure, it prints the error to stderr.
  279. */
  280. int get_root_cgroup(void)
  281. {
  282. int fd;
  283. fd = open(CGROUP_MOUNT_PATH, O_RDONLY);
  284. if (fd < 0) {
  285. log_err("Opening root cgroup");
  286. return -1;
  287. }
  288. return fd;
  289. }
  290. /**
  291. * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
  292. * @relative_path: The cgroup path, relative to the workdir, to join
  293. *
  294. * This function creates a cgroup under the top level workdir and returns the
  295. * file descriptor. It is idempotent.
  296. *
  297. * On success, it returns the file descriptor. On failure it returns -1.
  298. * If there is a failure, it prints the error to stderr.
  299. */
  300. int create_and_get_cgroup(const char *relative_path)
  301. {
  302. char cgroup_path[PATH_MAX + 1];
  303. int fd;
  304. format_cgroup_path(cgroup_path, relative_path);
  305. if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
  306. log_err("mkdiring cgroup %s .. %s", relative_path, cgroup_path);
  307. return -1;
  308. }
  309. fd = open(cgroup_path, O_RDONLY);
  310. if (fd < 0) {
  311. log_err("Opening Cgroup");
  312. return -1;
  313. }
  314. return fd;
  315. }
  316. /**
  317. * get_cgroup_id() - Get cgroup id for a particular cgroup path
  318. * @relative_path: The cgroup path, relative to the workdir, to join
  319. *
  320. * On success, it returns the cgroup id. On failure it returns 0,
  321. * which is an invalid cgroup id.
  322. * If there is a failure, it prints the error to stderr.
  323. */
  324. unsigned long long get_cgroup_id(const char *relative_path)
  325. {
  326. int dirfd, err, flags, mount_id, fhsize;
  327. union {
  328. unsigned long long cgid;
  329. unsigned char raw_bytes[8];
  330. } id;
  331. char cgroup_workdir[PATH_MAX + 1];
  332. struct file_handle *fhp, *fhp2;
  333. unsigned long long ret = 0;
  334. format_cgroup_path(cgroup_workdir, relative_path);
  335. dirfd = AT_FDCWD;
  336. flags = 0;
  337. fhsize = sizeof(*fhp);
  338. fhp = calloc(1, fhsize);
  339. if (!fhp) {
  340. log_err("calloc");
  341. return 0;
  342. }
  343. err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags);
  344. if (err >= 0 || fhp->handle_bytes != 8) {
  345. log_err("name_to_handle_at");
  346. goto free_mem;
  347. }
  348. fhsize = sizeof(struct file_handle) + fhp->handle_bytes;
  349. fhp2 = realloc(fhp, fhsize);
  350. if (!fhp2) {
  351. log_err("realloc");
  352. goto free_mem;
  353. }
  354. err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags);
  355. fhp = fhp2;
  356. if (err < 0) {
  357. log_err("name_to_handle_at");
  358. goto free_mem;
  359. }
  360. memcpy(id.raw_bytes, fhp->f_handle, 8);
  361. ret = id.cgid;
  362. free_mem:
  363. free(fhp);
  364. return ret;
  365. }
  366. int cgroup_setup_and_join(const char *path) {
  367. int cg_fd;
  368. if (setup_cgroup_environment()) {
  369. fprintf(stderr, "Failed to setup cgroup environment\n");
  370. return -EINVAL;
  371. }
  372. cg_fd = create_and_get_cgroup(path);
  373. if (cg_fd < 0) {
  374. fprintf(stderr, "Failed to create test cgroup\n");
  375. cleanup_cgroup_environment();
  376. return cg_fd;
  377. }
  378. if (join_cgroup(path)) {
  379. fprintf(stderr, "Failed to join cgroup\n");
  380. cleanup_cgroup_environment();
  381. return -EINVAL;
  382. }
  383. return cg_fd;
  384. }
  385. /**
  386. * setup_classid_environment() - Setup the cgroupv1 net_cls environment
  387. *
  388. * After calling this function, cleanup_classid_environment should be called
  389. * once testing is complete.
  390. *
  391. * This function will print an error to stderr and return 1 if it is unable
  392. * to setup the cgroup environment. If setup is successful, 0 is returned.
  393. */
  394. int setup_classid_environment(void)
  395. {
  396. char cgroup_workdir[PATH_MAX + 1];
  397. format_classid_path(cgroup_workdir);
  398. if (mount("tmpfs", CGROUP_MOUNT_DFLT, "tmpfs", 0, NULL) &&
  399. errno != EBUSY) {
  400. log_err("mount cgroup base");
  401. return 1;
  402. }
  403. if (mkdir(NETCLS_MOUNT_PATH, 0777) && errno != EEXIST) {
  404. log_err("mkdir cgroup net_cls");
  405. return 1;
  406. }
  407. if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls") &&
  408. errno != EBUSY) {
  409. log_err("mount cgroup net_cls");
  410. return 1;
  411. }
  412. cleanup_classid_environment();
  413. if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
  414. log_err("mkdir cgroup work dir");
  415. return 1;
  416. }
  417. return 0;
  418. }
  419. /**
  420. * set_classid() - Set a cgroupv1 net_cls classid
  421. * @id: the numeric classid
  422. *
  423. * Writes the passed classid into the cgroup work dir's net_cls.classid
  424. * file in order to later on trigger socket tagging.
  425. *
  426. * On success, it returns 0, otherwise on failure it returns 1. If there
  427. * is a failure, it prints the error to stderr.
  428. */
  429. int set_classid(unsigned int id)
  430. {
  431. char cgroup_workdir[PATH_MAX - 42];
  432. char cgroup_classid_path[PATH_MAX + 1];
  433. int fd, rc = 0;
  434. format_classid_path(cgroup_workdir);
  435. snprintf(cgroup_classid_path, sizeof(cgroup_classid_path),
  436. "%s/net_cls.classid", cgroup_workdir);
  437. fd = open(cgroup_classid_path, O_WRONLY);
  438. if (fd < 0) {
  439. log_err("Opening cgroup classid: %s", cgroup_classid_path);
  440. return 1;
  441. }
  442. if (dprintf(fd, "%u\n", id) < 0) {
  443. log_err("Setting cgroup classid");
  444. rc = 1;
  445. }
  446. close(fd);
  447. return rc;
  448. }
  449. /**
  450. * join_classid() - Join a cgroupv1 net_cls classid
  451. *
  452. * This function expects the cgroup work dir to be already created, as we
  453. * join it here. This causes the process sockets to be tagged with the given
  454. * net_cls classid.
  455. *
  456. * On success, it returns 0, otherwise on failure it returns 1.
  457. */
  458. int join_classid(void)
  459. {
  460. char cgroup_workdir[PATH_MAX + 1];
  461. format_classid_path(cgroup_workdir);
  462. return join_cgroup_from_top(cgroup_workdir);
  463. }
  464. /**
  465. * cleanup_classid_environment() - Cleanup the cgroupv1 net_cls environment
  466. *
  467. * At call time, it moves the calling process to the root cgroup, and then
  468. * runs the deletion process.
  469. *
  470. * On failure, it will print an error to stderr, and try to continue.
  471. */
  472. void cleanup_classid_environment(void)
  473. {
  474. char cgroup_workdir[PATH_MAX + 1];
  475. format_classid_path(cgroup_workdir);
  476. join_cgroup_from_top(NETCLS_MOUNT_PATH);
  477. nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
  478. }