1e31d33fcSYiyang Chen // SPDX-License-Identifier: GPL-2.0 2e31d33fcSYiyang Chen #define _GNU_SOURCE 3e31d33fcSYiyang Chen 4e31d33fcSYiyang Chen #include <errno.h> 5e31d33fcSYiyang Chen #include <fcntl.h> 6e31d33fcSYiyang Chen #include <linux/cgroupstats.h> 7e31d33fcSYiyang Chen #include <linux/genetlink.h> 8e31d33fcSYiyang Chen #include <linux/netlink.h> 9*0a2418c3SYiyang Chen #include <stdbool.h> 10e31d33fcSYiyang Chen #include <stdint.h> 11e31d33fcSYiyang Chen #include <stdio.h> 12e31d33fcSYiyang Chen #include <stdlib.h> 13e31d33fcSYiyang Chen #include <string.h> 14*0a2418c3SYiyang Chen #include <sys/mount.h> 15e31d33fcSYiyang Chen #include <sys/socket.h> 16e31d33fcSYiyang Chen #include <sys/types.h> 17e31d33fcSYiyang Chen #include <unistd.h> 18e31d33fcSYiyang Chen 19*0a2418c3SYiyang Chen #include "netlink_helper.h" 20e31d33fcSYiyang Chen #include "kselftest.h" 21e31d33fcSYiyang Chen 22e31d33fcSYiyang Chen static int send_cgroupstats_cmd(int fd, int family_id, uint32_t cgroup_fd, 23e31d33fcSYiyang Chen int flags) 24e31d33fcSYiyang Chen { 25e31d33fcSYiyang Chen struct { 26e31d33fcSYiyang Chen struct nlmsghdr nlh; 27e31d33fcSYiyang Chen struct genlmsghdr genl; 28e31d33fcSYiyang Chen char buf[256]; 29e31d33fcSYiyang Chen } req = { 0 }; 30e31d33fcSYiyang Chen struct nlattr *na; 31e31d33fcSYiyang Chen 32e31d33fcSYiyang Chen req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); 33e31d33fcSYiyang Chen req.nlh.nlmsg_type = family_id; 34e31d33fcSYiyang Chen req.nlh.nlmsg_flags = NLM_F_REQUEST | flags; 35e31d33fcSYiyang Chen req.nlh.nlmsg_seq = 2; 36e31d33fcSYiyang Chen req.nlh.nlmsg_pid = getpid(); 37e31d33fcSYiyang Chen 38e31d33fcSYiyang Chen req.genl.cmd = CGROUPSTATS_CMD_GET; 39e31d33fcSYiyang Chen req.genl.version = 1; 40e31d33fcSYiyang Chen 41e31d33fcSYiyang Chen na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); 42e31d33fcSYiyang Chen na->nla_type = CGROUPSTATS_CMD_ATTR_FD; 43e31d33fcSYiyang Chen na->nla_len = NLA_HDRLEN + sizeof(cgroup_fd); 44e31d33fcSYiyang Chen memcpy(nla_data(na), &cgroup_fd, sizeof(cgroup_fd)); 45e31d33fcSYiyang Chen req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); 46e31d33fcSYiyang Chen 47e31d33fcSYiyang Chen return send_request(fd, &req, req.nlh.nlmsg_len); 48e31d33fcSYiyang Chen } 49e31d33fcSYiyang Chen 50e31d33fcSYiyang Chen /* 51e31d33fcSYiyang Chen * Receive and decode a cgroupstats response. 52e31d33fcSYiyang Chen * 53e31d33fcSYiyang Chen * Returns: 54e31d33fcSYiyang Chen * 0 — success, stats filled from CGROUPSTATS_CMD_NEW reply 55e31d33fcSYiyang Chen * <0 — NLMSG_ERROR errno (e.g. -EBADF, -EINVAL) 56e31d33fcSYiyang Chen */ 57e31d33fcSYiyang Chen static int recv_cgroupstats_response(int fd, struct cgroupstats *stats) 58e31d33fcSYiyang Chen { 59e31d33fcSYiyang Chen char resp[8192]; 60e31d33fcSYiyang Chen struct nlmsghdr *nlh; 61e31d33fcSYiyang Chen struct genlmsghdr *genl; 62e31d33fcSYiyang Chen struct nlattr *na; 63e31d33fcSYiyang Chen int len; 64e31d33fcSYiyang Chen int rem; 65e31d33fcSYiyang Chen 66e31d33fcSYiyang Chen memset(stats, 0, sizeof(*stats)); 67e31d33fcSYiyang Chen 68e31d33fcSYiyang Chen len = recv(fd, resp, sizeof(resp), 0); 69e31d33fcSYiyang Chen if (len < 0) 70e31d33fcSYiyang Chen return -errno; 71e31d33fcSYiyang Chen 72e31d33fcSYiyang Chen for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); 73e31d33fcSYiyang Chen nlh = NLMSG_NEXT(nlh, len)) { 74e31d33fcSYiyang Chen if (nlh->nlmsg_type == NLMSG_ERROR) { 75e31d33fcSYiyang Chen struct nlmsgerr *err = NLMSG_DATA(nlh); 76e31d33fcSYiyang Chen 77e31d33fcSYiyang Chen return err->error; 78e31d33fcSYiyang Chen } 79e31d33fcSYiyang Chen 80e31d33fcSYiyang Chen genl = (struct genlmsghdr *)NLMSG_DATA(nlh); 81e31d33fcSYiyang Chen if (genl->cmd != CGROUPSTATS_CMD_NEW) 82e31d33fcSYiyang Chen continue; 83e31d33fcSYiyang Chen 84e31d33fcSYiyang Chen rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; 85e31d33fcSYiyang Chen na = (struct nlattr *)((char *)genl + GENL_HDRLEN); 86*0a2418c3SYiyang Chen while (nla_ok(na, rem)) { 87e31d33fcSYiyang Chen if (na->nla_type == CGROUPSTATS_TYPE_CGROUP_STATS) { 88e31d33fcSYiyang Chen memcpy(stats, nla_data(na), sizeof(*stats)); 89e31d33fcSYiyang Chen return 0; 90e31d33fcSYiyang Chen } 91*0a2418c3SYiyang Chen na = nla_next(na, &rem); 92e31d33fcSYiyang Chen } 93e31d33fcSYiyang Chen } 94e31d33fcSYiyang Chen 95e31d33fcSYiyang Chen return -EIO; 96e31d33fcSYiyang Chen } 97e31d33fcSYiyang Chen 98e31d33fcSYiyang Chen /* mkdtemp() modifies the template in place, so this cannot be const. */ 99e31d33fcSYiyang Chen static char cg_mountpoint[32]; 100e31d33fcSYiyang Chen static bool cg_mounted; 101e31d33fcSYiyang Chen 102e31d33fcSYiyang Chen static int setup_cgroup_v1(void) 103e31d33fcSYiyang Chen { 104e31d33fcSYiyang Chen strcpy(cg_mountpoint, "/tmp/cgstats_test_XXXXXX"); 105e31d33fcSYiyang Chen 106e31d33fcSYiyang Chen if (!mkdtemp(cg_mountpoint)) 107e31d33fcSYiyang Chen return -errno; 108e31d33fcSYiyang Chen 109e31d33fcSYiyang Chen if (mount("cgstats_test", cg_mountpoint, "cgroup", 0, 110e31d33fcSYiyang Chen "none,name=cgstats_test") < 0) { 111e31d33fcSYiyang Chen int ret = -errno; 112e31d33fcSYiyang Chen 113e31d33fcSYiyang Chen rmdir(cg_mountpoint); 114e31d33fcSYiyang Chen return ret; 115e31d33fcSYiyang Chen } 116e31d33fcSYiyang Chen 117e31d33fcSYiyang Chen cg_mounted = true; 118e31d33fcSYiyang Chen return 0; 119e31d33fcSYiyang Chen } 120e31d33fcSYiyang Chen 121e31d33fcSYiyang Chen static void cleanup_cgroup_v1(void) 122e31d33fcSYiyang Chen { 123e31d33fcSYiyang Chen if (!cg_mounted) 124e31d33fcSYiyang Chen return; 125e31d33fcSYiyang Chen umount2(cg_mountpoint, MNT_DETACH); 126e31d33fcSYiyang Chen rmdir(cg_mountpoint); 127e31d33fcSYiyang Chen cg_mounted = false; 128e31d33fcSYiyang Chen } 129e31d33fcSYiyang Chen 130e31d33fcSYiyang Chen int main(void) 131e31d33fcSYiyang Chen { 132e31d33fcSYiyang Chen struct cgroupstats stats; 133e31d33fcSYiyang Chen uint64_t total_tasks; 134e31d33fcSYiyang Chen int family_id; 135e31d33fcSYiyang Chen int nl_fd; 136e31d33fcSYiyang Chen int cg_fd; 137e31d33fcSYiyang Chen int ret; 138e31d33fcSYiyang Chen 139e31d33fcSYiyang Chen ksft_print_header(); 140e31d33fcSYiyang Chen 141e31d33fcSYiyang Chen nl_fd = netlink_open(); 142e31d33fcSYiyang Chen if (nl_fd < 0) 143e31d33fcSYiyang Chen ksft_exit_skip("failed to open generic netlink socket: %s\n", 144e31d33fcSYiyang Chen strerror(-nl_fd)); 145e31d33fcSYiyang Chen 146e31d33fcSYiyang Chen family_id = get_family_id(nl_fd, TASKSTATS_GENL_NAME); 147e31d33fcSYiyang Chen if (family_id < 0) 148e31d33fcSYiyang Chen ksft_exit_skip("taskstats generic netlink family unavailable: %s\n", 149e31d33fcSYiyang Chen strerror(-family_id)); 150e31d33fcSYiyang Chen 151e31d33fcSYiyang Chen ksft_set_plan(3); 152e31d33fcSYiyang Chen 153e31d33fcSYiyang Chen /* 154e31d33fcSYiyang Chen * Test 1: mount a private cgroup v1 hierarchy, query it, and 155e31d33fcSYiyang Chen * verify the response contains sane task counts. If the test 156e31d33fcSYiyang Chen * environment cannot create a private cgroup v1 mount, skip this 157e31d33fcSYiyang Chen * case and continue with the unprivileged regression checks below. 158e31d33fcSYiyang Chen */ 159e31d33fcSYiyang Chen ret = setup_cgroup_v1(); 160e31d33fcSYiyang Chen if (ret) { 161e31d33fcSYiyang Chen ksft_test_result_skip("cgroupstats query: cannot mount cgroup v1: %s\n", 162e31d33fcSYiyang Chen strerror(-ret)); 163e31d33fcSYiyang Chen } else { 164e31d33fcSYiyang Chen cg_fd = open(cg_mountpoint, O_RDONLY | O_DIRECTORY); 165e31d33fcSYiyang Chen if (cg_fd < 0) { 166e31d33fcSYiyang Chen ksft_test_result_fail("cgroupstats query: open mountpoint: %s\n", 167e31d33fcSYiyang Chen strerror(errno)); 168e31d33fcSYiyang Chen } else { 169e31d33fcSYiyang Chen ret = send_cgroupstats_cmd(nl_fd, family_id, 170e31d33fcSYiyang Chen (uint32_t)cg_fd, 0); 171e31d33fcSYiyang Chen if (ret) { 172e31d33fcSYiyang Chen ksft_test_result_fail("cgroupstats query: send: %s\n", 173e31d33fcSYiyang Chen strerror(-ret)); 174e31d33fcSYiyang Chen } else { 175e31d33fcSYiyang Chen ret = recv_cgroupstats_response(nl_fd, &stats); 176e31d33fcSYiyang Chen if (ret < 0) { 177e31d33fcSYiyang Chen ksft_test_result_fail("cgroupstats query: %s\n", 178e31d33fcSYiyang Chen strerror(-ret)); 179e31d33fcSYiyang Chen } else { 180e31d33fcSYiyang Chen total_tasks = (uint64_t)stats.nr_sleeping + 181e31d33fcSYiyang Chen (uint64_t)stats.nr_running + 182e31d33fcSYiyang Chen (uint64_t)stats.nr_stopped + 183e31d33fcSYiyang Chen (uint64_t)stats.nr_uninterruptible + 184e31d33fcSYiyang Chen (uint64_t)stats.nr_io_wait; 185e31d33fcSYiyang Chen 186e31d33fcSYiyang Chen ksft_print_msg("cgroupstats query: total_tasks=%llu\n", 187e31d33fcSYiyang Chen (unsigned long long)total_tasks); 188e31d33fcSYiyang Chen 189e31d33fcSYiyang Chen ksft_test_result(total_tasks > 0, 190e31d33fcSYiyang Chen "cgroupstats query returns valid stats\n"); 191e31d33fcSYiyang Chen } 192e31d33fcSYiyang Chen } 193e31d33fcSYiyang Chen close(cg_fd); 194e31d33fcSYiyang Chen } 195e31d33fcSYiyang Chen } 196e31d33fcSYiyang Chen cleanup_cgroup_v1(); 197e31d33fcSYiyang Chen 198e31d33fcSYiyang Chen /* 199e31d33fcSYiyang Chen * Test 2: invalid fd without NLM_F_ACK. The kernel should 200e31d33fcSYiyang Chen * return -EBADF via NLMSG_ERROR regardless of whether the 201e31d33fcSYiyang Chen * client requested an explicit ACK. 202e31d33fcSYiyang Chen */ 203e31d33fcSYiyang Chen ret = send_cgroupstats_cmd(nl_fd, family_id, 0xFFFFFFFF, 0); 204e31d33fcSYiyang Chen if (ret) 205e31d33fcSYiyang Chen ksft_exit_fail_msg("send test 2 failed: %s\n", strerror(-ret)); 206e31d33fcSYiyang Chen 207e31d33fcSYiyang Chen ret = recv_cgroupstats_response(nl_fd, &stats); 208e31d33fcSYiyang Chen ksft_print_msg("bad fd (no ACK): response=%d (%s)\n", 209e31d33fcSYiyang Chen ret, ret < 0 ? strerror(-ret) : "unexpected success"); 210e31d33fcSYiyang Chen ksft_test_result(ret == -EBADF, 211e31d33fcSYiyang Chen "cgroupstats rejects bad fd without NLM_F_ACK\n"); 212e31d33fcSYiyang Chen 213e31d33fcSYiyang Chen /* 214e31d33fcSYiyang Chen * Test 3: invalid fd with NLM_F_ACK. Same expectation as 215e31d33fcSYiyang Chen * test 2, but exercised through a different netlink flag 216e31d33fcSYiyang Chen * path in the kernel's ack/error handling. 217e31d33fcSYiyang Chen */ 218e31d33fcSYiyang Chen ret = send_cgroupstats_cmd(nl_fd, family_id, 0xFFFFFFFF, NLM_F_ACK); 219e31d33fcSYiyang Chen if (ret) 220e31d33fcSYiyang Chen ksft_exit_fail_msg("send test 3 failed: %s\n", strerror(-ret)); 221e31d33fcSYiyang Chen 222e31d33fcSYiyang Chen ret = recv_cgroupstats_response(nl_fd, &stats); 223e31d33fcSYiyang Chen ksft_print_msg("bad fd (with ACK): response=%d (%s)\n", 224e31d33fcSYiyang Chen ret, ret < 0 ? strerror(-ret) : "unexpected success"); 225e31d33fcSYiyang Chen ksft_test_result(ret == -EBADF, 226e31d33fcSYiyang Chen "cgroupstats rejects bad fd with NLM_F_ACK\n"); 227e31d33fcSYiyang Chen 228e31d33fcSYiyang Chen close(nl_fd); 229e31d33fcSYiyang Chen ksft_finished(); 230e31d33fcSYiyang Chen return ksft_get_fail_cnt() ? KSFT_FAIL : KSFT_PASS; 231e31d33fcSYiyang Chen } 232