gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/mq.cc (about) 1 // Copyright 2021 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <fcntl.h> 16 #include <mqueue.h> 17 #include <sched.h> 18 #include <sys/poll.h> 19 #include <sys/stat.h> 20 #include <unistd.h> 21 22 #include <string> 23 24 #include "test/util/capability_util.h" 25 #include "test/util/cleanup.h" 26 #include "test/util/fs_util.h" 27 #include "test/util/mount_util.h" 28 #include "test/util/posix_error.h" 29 #include "test/util/temp_path.h" 30 #include "test/util/test_util.h" 31 32 #define NAME_MAX 255 33 34 namespace gvisor { 35 namespace testing { 36 namespace { 37 38 // PosixQueue is a RAII class used to automatically clean POSIX message queues. 39 class PosixQueue { 40 public: 41 PosixQueue(mqd_t fd, std::string name) : fd_(fd), name_(std::string(name)) {} 42 PosixQueue(const PosixQueue&) = delete; 43 PosixQueue& operator=(const PosixQueue&) = delete; 44 45 // Move constructor. 46 PosixQueue(PosixQueue&& q) { 47 fd_ = q.fd_; 48 name_ = q.name_; 49 // Call PosixQueue::release, to prevent the object being released from 50 // unlinking the underlying queue. 51 q.release(); 52 } 53 54 ~PosixQueue() { 55 if (fd_ != -1) { 56 EXPECT_THAT(mq_close(fd_), SyscallSucceeds()); 57 EXPECT_THAT(mq_unlink(name_.c_str()), SyscallSucceeds()); 58 } 59 } 60 61 mqd_t fd() { return fd_; } 62 63 const char* name() { return name_.c_str(); } 64 65 mqd_t release() { 66 mqd_t old = fd_; 67 fd_ = -1; 68 return old; 69 } 70 71 private: 72 mqd_t fd_; 73 std::string name_; 74 }; 75 76 // MqOpen wraps mq_open(3) using a given name. 77 PosixErrorOr<PosixQueue> MqOpen(std::string name, int oflag) { 78 mqd_t fd = mq_open(name.c_str(), oflag); 79 if (fd == -1) { 80 return PosixError(errno, absl::StrFormat("mq_open(%s, %d)", name, oflag)); 81 } 82 return PosixQueue(fd, name); 83 } 84 85 // MqOpen wraps mq_open(3) using a given name. 86 PosixErrorOr<PosixQueue> MqOpen(int oflag, mode_t mode, struct mq_attr* attr) { 87 auto name = "/" + NextTempBasename(); 88 mqd_t fd = mq_open(name.c_str(), oflag, mode, attr); 89 if (fd == -1) { 90 return PosixError(errno, absl::StrFormat("mq_open(%d)", oflag)); 91 } 92 return PosixQueue(fd, name); 93 } 94 95 // MqOpen wraps mq_open(3) using a generated name. 96 PosixErrorOr<PosixQueue> MqOpen(std::string name, int oflag, mode_t mode, 97 struct mq_attr* attr) { 98 mqd_t fd = mq_open(name.c_str(), oflag, mode, attr); 99 if (fd == -1) { 100 return PosixError(errno, absl::StrFormat("mq_open(%d)", oflag)); 101 } 102 return PosixQueue(fd, name); 103 } 104 105 // MqUnlink wraps mq_unlink(2). 106 PosixError MqUnlink(std::string name) { 107 int err = mq_unlink(name.c_str()); 108 if (err == -1) { 109 return PosixError(errno, absl::StrFormat("mq_unlink(%s)", name.c_str())); 110 } 111 return NoError(); 112 } 113 114 // MqClose wraps mq_close(2). 115 PosixError MqClose(mqd_t fd) { 116 int err = mq_close(fd); 117 if (err == -1) { 118 return PosixError(errno, absl::StrFormat("mq_close(%d)", fd)); 119 } 120 return NoError(); 121 } 122 123 // Test simple opening and closing of a message queue. 124 TEST(MqTest, Open) { 125 ASSERT_NO_ERRNO(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 126 } 127 128 TEST(MqTest, ModeWithFileType) { 129 // S_IFIFO should be ignored. 130 ASSERT_NO_ERRNO(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777 | S_IFIFO, nullptr)); 131 } 132 133 // Test mq_open(2) after mq_unlink(2). 134 TEST(MqTest, OpenAfterUnlink) { 135 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 136 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 137 138 ASSERT_NO_ERRNO(MqUnlink(queue.name())); 139 EXPECT_THAT(MqOpen(queue.name(), O_RDWR), PosixErrorIs(ENOENT)); 140 ASSERT_NO_ERRNO(MqClose(queue.release())); 141 } 142 143 // Test using invalid args with mq_open. 144 TEST(MqTest, OpenInvalidArgs) { 145 // Name must start with a slash. 146 EXPECT_THAT(MqOpen("test", O_RDWR), PosixErrorIs(EINVAL)); 147 148 // Name can't contain more that one slash. 149 EXPECT_THAT(MqOpen("/test/name", O_RDWR), PosixErrorIs(EACCES)); 150 151 // Both "." and ".." can't be used as queue names. 152 EXPECT_THAT(MqOpen(".", O_RDWR), PosixErrorIs(EINVAL)); 153 EXPECT_THAT(MqOpen("..", O_RDWR), PosixErrorIs(EINVAL)); 154 155 // mq_attr's mq_maxmsg and mq_msgsize must be > 0. 156 struct mq_attr attr; 157 attr.mq_maxmsg = -1; 158 attr.mq_msgsize = 10; 159 160 EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, &attr), 161 PosixErrorIs(EINVAL)); 162 163 attr.mq_maxmsg = 10; 164 attr.mq_msgsize = -1; 165 166 EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, &attr), 167 PosixErrorIs(EINVAL)); 168 169 // Names should be shorter than NAME_MAX. 170 char max[NAME_MAX + 3]; 171 max[0] = '/'; 172 for (size_t i = 1; i < NAME_MAX + 2; i++) { 173 max[i] = 'a'; 174 } 175 max[NAME_MAX + 2] = '\0'; 176 177 EXPECT_THAT(MqOpen(std::string(max), O_RDWR | O_CREAT | O_EXCL, 0777, &attr), 178 PosixErrorIs(ENAMETOOLONG)); 179 } 180 181 // Test creating a queue that already exists. 182 TEST(MqTest, CreateAlreadyExists) { 183 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 184 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 185 186 EXPECT_THAT(MqOpen(queue.name(), O_RDWR | O_CREAT | O_EXCL, 0777, nullptr), 187 PosixErrorIs(EEXIST)); 188 } 189 190 // Test opening a queue that doesn't exists. 191 TEST(MqTest, NoQueueExists) { 192 // Choose a name to pass that's unlikely to exist if the test is run locally. 193 EXPECT_THAT(MqOpen("/gvisor-mq-test-nonexistent-queue", O_RDWR), 194 PosixErrorIs(ENOENT)); 195 } 196 197 // Test trying to re-open a queue with invalid permissions. 198 TEST(MqTest, OpenNoAccess) { 199 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 200 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0000, nullptr)); 201 202 EXPECT_THAT(MqOpen(queue.name(), O_RDONLY), PosixErrorIs(EACCES)); 203 EXPECT_THAT(MqOpen(queue.name(), O_WRONLY), PosixErrorIs(EACCES)); 204 EXPECT_THAT(MqOpen(queue.name(), O_RDWR), PosixErrorIs(EACCES)); 205 } 206 207 // Test trying to re-open a read-only queue for write. 208 TEST(MqTest, OpenReadAccess) { 209 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 210 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0400, nullptr)); 211 212 EXPECT_THAT(MqOpen(queue.name(), O_WRONLY), PosixErrorIs(EACCES)); 213 EXPECT_NO_ERRNO(MqOpen(queue.name(), O_RDONLY)); 214 queue.release(); 215 } 216 217 // Test trying to re-open a write-only queue for read. 218 TEST(MqTest, OpenWriteAccess) { 219 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 220 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0200, nullptr)); 221 222 EXPECT_THAT(MqOpen(queue.name(), O_RDONLY), PosixErrorIs(EACCES)); 223 EXPECT_NO_ERRNO(MqOpen(queue.name(), O_WRONLY)); 224 queue.release(); 225 } 226 227 // Test changing IPC namespace. 228 TEST(MqTest, ChangeIpcNamespace) { 229 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); 230 231 // When changing IPC namespaces, Linux doesn't invalidate or close the 232 // previously opened file descriptions and allows operations to be performed 233 // on them normally, until they're closed. 234 // 235 // To test this we create a new queue, use unshare(CLONE_NEWIPC) to change 236 // into a new IPC namespace, and trying performing a read(2) on the queue. 237 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 238 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 239 240 // As mq_unlink(2) uses queue's name, it should fail after changing IPC 241 // namespace. To clean the queue, we should unlink it now, this should not 242 // cause a problem, as the queue presists until the last mq_close(2). 243 ASSERT_NO_ERRNO(MqUnlink(queue.name())); 244 245 ASSERT_THAT(unshare(CLONE_NEWIPC), SyscallSucceeds()); 246 247 const size_t msgSize = 60; 248 char queueRead[msgSize]; 249 ASSERT_THAT(read(queue.fd(), &queueRead[0], msgSize - 1), SyscallSucceeds()); 250 251 ASSERT_NO_ERRNO(MqClose(queue.release())); 252 253 // Unlinking should fail now after changing IPC namespace. 254 EXPECT_THAT(MqUnlink(queue.name()), PosixErrorIs(ENOENT)); 255 } 256 257 // Test mounting the mqueue filesystem. 258 TEST(MqTest, Mount) { 259 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); 260 261 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 262 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 263 264 auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 265 ASSERT_NO_ERRNO(Mount("none", dir.path(), "mqueue", 0, "", 0)); 266 } 267 268 // Test mounting the mqueue filesystem to several places. 269 TEST(MqTest, MountSeveral) { 270 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); 271 constexpr int numMounts = 3; 272 273 // mountDirs should outlive mountCUs and queue so that its destructor succeeds 274 // in unlinking the mountpoints and does not interfere with queue destruction. 275 testing::TempPath mountDirs[numMounts]; 276 testing::Cleanup mountCUs[numMounts]; 277 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 278 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 279 280 for (int i = 0; i < numMounts; ++i) { 281 mountDirs[i] = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 282 mountCUs[i] = ASSERT_NO_ERRNO_AND_VALUE( 283 Mount("none", mountDirs[i].path(), "mqueue", 0, "", 0)); 284 } 285 286 // Ensure that queue is visible from all mounts. 287 for (int i = 0; i < numMounts; ++i) { 288 ASSERT_NO_ERRNO(Stat(JoinPath(mountDirs[i].path(), queue.name()))); 289 } 290 } 291 292 // Test mounting mqueue and opening a queue as normal file. 293 TEST(MqTest, OpenAsFile) { 294 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); 295 296 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 297 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 298 299 auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 300 auto mnt = 301 ASSERT_NO_ERRNO_AND_VALUE(Mount("none", dir.path(), "mqueue", 0, "", 0)); 302 303 // Open queue using open(2). 304 auto fd = ASSERT_NO_ERRNO_AND_VALUE( 305 Open(JoinPath(dir.path(), queue.name()), O_RDONLY)); 306 307 const size_t msgSize = 60; 308 char queueRead[msgSize]; 309 queueRead[msgSize - 1] = '\0'; 310 311 ASSERT_THAT(read(fd.get(), &queueRead[0], msgSize - 1), SyscallSucceeds()); 312 313 std::string want( 314 "QSIZE:0 NOTIFY:0 SIGNO:0 NOTIFY_PID:0 "); 315 std::string got(queueRead); 316 EXPECT_EQ(got, want); 317 } 318 319 // Test removing a queue using unlink(2). 320 TEST(MqTest, UnlinkAsFile) { 321 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); 322 323 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 324 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 325 326 auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 327 auto mnt = 328 ASSERT_NO_ERRNO_AND_VALUE(Mount("none", dir.path(), "mqueue", 0, "", 0)); 329 330 ASSERT_NO_ERRNO( 331 UnlinkAt(FileDescriptor(), JoinPath(dir.path(), queue.name()), 0)); 332 333 // Trying to unlink again should fail. 334 EXPECT_THAT(MqUnlink(queue.name()), PosixErrorIs(ENOENT)); 335 queue.release(); 336 } 337 338 // Test read(2) from an empty queue. 339 TEST(MqTest, ReadEmpty) { 340 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 341 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 342 343 const size_t msgSize = 60; 344 char queueRead[msgSize]; 345 queueRead[msgSize - 1] = '\0'; 346 347 ASSERT_THAT(read(queue.fd(), &queueRead[0], msgSize - 1), SyscallSucceeds()); 348 349 std::string want( 350 "QSIZE:0 NOTIFY:0 SIGNO:0 NOTIFY_PID:0 "); 351 std::string got(queueRead); 352 EXPECT_EQ(got, want); 353 } 354 355 // Test poll(2) on an empty queue. 356 TEST(MqTest, PollEmpty) { 357 PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( 358 MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); 359 360 struct pollfd pfd; 361 pfd.fd = queue.fd(); 362 pfd.events = POLLOUT | POLLIN | POLLRDNORM | POLLWRNORM; 363 364 ASSERT_THAT(poll(&pfd, 1, -1), SyscallSucceeds()); 365 ASSERT_EQ(pfd.revents, POLLOUT | POLLWRNORM); 366 } 367 368 } // namespace 369 } // namespace testing 370 } // namespace gvisor