// SPDX-License-Identifier: GPL-2.0 /* * Exercise the RDS getsockopt() paths that were converted to the * getsockopt_iter() / sockopt_t callback. * * Three distinct paths are covered: * * - RDS_RECVERR and SO_RDS_TRANSPORT, which now return their int value * through copy_to_iter() and report the written length in opt->optlen. * * - RDS_INFO_*, which pins the userspace buffer with * iov_iter_extract_pages() (including a non-zero starting page offset) * and lets the info producers memcpy the snapshot in under a spinlock. * * The kvec (in-kernel buffer) -> -EOPNOTSUPP path of rds_info_getsockopt() * is not reachable from a userspace getsockopt() and so is not tested here. */ #include #include #include #include #include #include #include #include "../../kselftest_harness.h" #ifndef AF_RDS #define AF_RDS 21 #endif FIXTURE(rds) { int fd; }; FIXTURE_SETUP(rds) { self->fd = socket(AF_RDS, SOCK_SEQPACKET, 0); if (self->fd < 0) SKIP(return, "AF_RDS unavailable (errno %d) - load the rds module", errno); } FIXTURE_TEARDOWN(rds) { if (self->fd >= 0) close(self->fd); } /* RDS_RECVERR defaults to 0 and is reported back as a 4-byte int. */ TEST_F(rds, recverr_default) { socklen_t len = sizeof(int); int val = 0xdeadbeef; ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len)); EXPECT_EQ(sizeof(int), len); EXPECT_EQ(0, val); } /* A value set via setsockopt() must be readable back unchanged. */ TEST_F(rds, recverr_set_get) { socklen_t len = sizeof(int); int val = 1; ASSERT_EQ(0, setsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, len)); val = 0; ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len)); EXPECT_EQ(sizeof(int), len); EXPECT_EQ(1, val); } /* A buffer smaller than an int is rejected with EINVAL, not silently. */ TEST_F(rds, recverr_short_buffer) { socklen_t len = sizeof(int) - 1; char buf[sizeof(int)]; EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, buf, &len)); EXPECT_EQ(EINVAL, errno); } /* An unbound socket reports RDS_TRANS_NONE for SO_RDS_TRANSPORT. */ TEST_F(rds, transport_unbound) { socklen_t len = sizeof(int); int val = 0; ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, &val, &len)); EXPECT_EQ(sizeof(int), len); EXPECT_EQ(RDS_TRANS_NONE, (unsigned int)val); } TEST_F(rds, transport_short_buffer) { socklen_t len = sizeof(int) - 1; char buf[sizeof(int)]; EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, buf, &len)); EXPECT_EQ(EINVAL, errno); } /* * RDS_INFO_COUNTERS with a zero-length buffer is the "probe" call: it must * fail with ENOSPC and report the required snapshot size in optlen. */ TEST_F(rds, info_counters_probe) { socklen_t len = 0; EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &len)); EXPECT_EQ(ENOSPC, errno); EXPECT_GT(len, 0); /* The snapshot is an array of fixed-size counter records. */ EXPECT_EQ(0, len % (socklen_t)sizeof(struct rds_info_counter)); } /* * A real snapshot into an unaligned userspace buffer exercises the * iov_iter_extract_pages() path, including the non-zero offset0 handling * that the patch reworked. Place the buffer at a non-page-aligned address * spanning into the next page to make sure multi-page pinning works too. */ TEST_F(rds, info_counters_snapshot) { struct rds_info_counter *ctr; socklen_t need = 0, len; long pagesz = sysconf(_SC_PAGESIZE); size_t offset, map_len; unsigned int i, n; char *region, *buf; int ret; /* Probe for the required size. */ getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need); ASSERT_GT(need, 0); /* * Place the buffer at a non-page-aligned offset that runs past the * first page boundary, and size the mapping from the probed length so * the test keeps working if the counter set grows. */ offset = pagesz - 64; map_len = ((offset + need + pagesz - 1) / pagesz) * pagesz; region = mmap(NULL, map_len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); ASSERT_NE(MAP_FAILED, region); buf = region + offset; /* * On success the RDS_INFO path returns the positive per-element size * (lens.each) rather than 0, and writes the full snapshot length back * into optlen. */ len = need; ret = getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, buf, &len); ASSERT_GE(ret, 0) { TH_LOG("getsockopt snapshot failed: errno %d", errno); } EXPECT_EQ(sizeof(struct rds_info_counter), ret); EXPECT_EQ(need, len); /* The counter names must be NUL-terminated, non-empty strings. */ ctr = (struct rds_info_counter *)buf; n = len / sizeof(*ctr); ASSERT_GT(n, 0); for (i = 0; i < n; i++) { size_t namelen = strnlen((char *)ctr[i].name, sizeof(ctr[i].name)); EXPECT_GT(namelen, 0); EXPECT_LT(namelen, sizeof(ctr[i].name)); } munmap(region, map_len); } /* * A non-zero but too-small buffer must report ENOSPC and the full required * length, without corrupting memory past the buffer. */ TEST_F(rds, info_counters_short_buffer) { socklen_t need = 0, len; char small[sizeof(struct rds_info_counter)]; getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need); ASSERT_GT(need, 0); /* Ask with a buffer guaranteed smaller than the full snapshot. */ if (need <= (socklen_t)sizeof(small)) SKIP(return, "snapshot fits in one record; nothing to test"); len = 1; /* < sizeof(struct rds_info_counter) */ EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, small, &len)); EXPECT_EQ(ENOSPC, errno); EXPECT_EQ(need, len); } TEST_HARNESS_MAIN