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