diff options
author | Zyad A. Ali <zyad.ali.me@gmail.com> | 2021-07-15 15:23:28 +0200 |
---|---|---|
committer | Zyad A. Ali <zyad.ali.me@gmail.com> | 2021-09-15 21:56:35 +0200 |
commit | fc8819f43cae94db8345060f0af00e013dc86098 (patch) | |
tree | ed3585202342ebfa699c54f19fc1e8db72272af3 /test | |
parent | 149ca009678edc580de9f0b1d54f551d376742cb (diff) |
Test creation and deletion of POSIX message queues.
Updates #136
Diffstat (limited to 'test')
-rw-r--r-- | test/syscalls/BUILD | 4 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 16 | ||||
-rw-r--r-- | test/syscalls/linux/mq.cc | 234 | ||||
-rw-r--r-- | test/util/temp_path.cc | 26 | ||||
-rw-r--r-- | test/util/temp_path.h | 3 |
5 files changed, 269 insertions, 14 deletions
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 3b55112e6..43854e80b 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -326,6 +326,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:mq_test", +) + +syscall_test( size = "medium", test = "//test/syscalls/linux:mremap_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index a4efd9486..ed2adfbc9 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -4171,6 +4171,22 @@ cc_binary( ) cc_binary( + name = "mq_test", + testonly = 1, + srcs = ["mq.cc"], + linkstatic = 1, + linkopts = [ + "-lrt", + ], + deps = [ + "//test/util:posix_error", + "//test/util:test_main", + "//test/util:temp_path", + "//test/util:test_util", + ], +) + +cc_binary( name = "semaphore_test", testonly = 1, # Android does not support XSI semaphores in r22. diff --git a/test/syscalls/linux/mq.cc b/test/syscalls/linux/mq.cc new file mode 100644 index 000000000..610d41fb6 --- /dev/null +++ b/test/syscalls/linux/mq.cc @@ -0,0 +1,234 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <fcntl.h> +#include <mqueue.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include "test/util/posix_error.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" + +#define NAME_MAX 255 + +namespace gvisor { +namespace testing { +namespace { + +using ::testing::_; + +// PosixQueue is a RAII class used to automatically clean POSIX message queues. +class PosixQueue { + public: + PosixQueue(mqd_t fd, std::string name) : fd_(fd), name_(std::string(name)) {} + PosixQueue(const PosixQueue&) = delete; + PosixQueue& operator=(const PosixQueue&) = delete; + + // Move constructor. + PosixQueue(PosixQueue&& q) : fd_(q.fd_), name_(q.name_) { + // Call PosixQueue::release, to prevent the object being released from + // unlinking the underlying queue. + q.release(); + } + + ~PosixQueue() { + if (fd_ != -1) { + EXPECT_THAT(mq_close(fd_), SyscallSucceeds()); + EXPECT_THAT(mq_unlink(name_.c_str()), SyscallSucceeds()); + } + } + + mqd_t fd() { return fd_; } + + const char* name() { return name_.c_str(); } + + mqd_t release() { + mqd_t old = fd_; + fd_ = -1; + return old; + } + + private: + mqd_t fd_; + std::string name_; +}; + +// MqOpen wraps mq_open(3) using a given name. +PosixErrorOr<PosixQueue> MqOpen(std::string name, int oflag) { + mqd_t fd = mq_open(name.c_str(), oflag); + if (fd == -1) { + return PosixError(errno, absl::StrFormat("mq_open(%s, %d)", name, oflag)); + } + return PosixQueue(fd, name); +} + +// MqOpen wraps mq_open(3) using a given name. +PosixErrorOr<PosixQueue> MqOpen(int oflag, mode_t mode, struct mq_attr* attr) { + auto name = "/" + NextTempBasename(); + mqd_t fd = mq_open(name.c_str(), oflag, mode, attr); + if (fd == -1) { + return PosixError(errno, absl::StrFormat("mq_open(%d)", oflag)); + } + return PosixQueue(fd, name); +} + +// MqOpen wraps mq_open(3) using a generated name. +PosixErrorOr<PosixQueue> MqOpen(std::string name, int oflag, mode_t mode, + struct mq_attr* attr) { + mqd_t fd = mq_open(name.c_str(), oflag, mode, attr); + if (fd == -1) { + return PosixError(errno, absl::StrFormat("mq_open(%d)", oflag)); + } + return PosixQueue(fd, name); +} + +// MqUnlink wraps mq_unlink(2). +PosixError MqUnlink(std::string name) { + int err = mq_unlink(name.c_str()); + if (err == -1) { + return PosixError(errno, absl::StrFormat("mq_unlink(%s)", name.c_str())); + } + return NoError(); +} + +// MqClose wraps mq_close(2). +PosixError MqClose(mqd_t fd) { + int err = mq_close(fd); + if (err == -1) { + return PosixError(errno, absl::StrFormat("mq_close(%d)", fd)); + } + return NoError(); +} + +// Test simple opening and closing of a message queue. +TEST(MqTest, Open) { + GTEST_SKIP(); + EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr), + IsPosixErrorOkAndHolds(_)); +} + +// Test mq_open(2) after mq_unlink(2). +TEST(MqTest, OpenAfterUnlink) { + GTEST_SKIP(); + + PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( + MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); + + ASSERT_NO_ERRNO(MqUnlink(queue.name())); + EXPECT_THAT(MqOpen(queue.name(), O_RDWR), PosixErrorIs(ENOENT)); + ASSERT_NO_ERRNO(MqClose(queue.release())); +} + +// Test using invalid args with mq_open. +TEST(MqTest, OpenInvalidArgs) { + GTEST_SKIP(); + + // Name must start with a slash. + EXPECT_THAT(MqOpen("test", O_RDWR), PosixErrorIs(EINVAL)); + + // Name can't contain more that one slash. + EXPECT_THAT(MqOpen("/test/name", O_RDWR), PosixErrorIs(EACCES)); + + // Both "." and ".." can't be used as queue names. + EXPECT_THAT(MqOpen(".", O_RDWR), PosixErrorIs(EINVAL)); + EXPECT_THAT(MqOpen("..", O_RDWR), PosixErrorIs(EINVAL)); + + // mq_attr's mq_maxmsg and mq_msgsize must be > 0. + struct mq_attr attr; + attr.mq_maxmsg = -1; + attr.mq_msgsize = 10; + + EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, &attr), + PosixErrorIs(EINVAL)); + + attr.mq_maxmsg = 10; + attr.mq_msgsize = -1; + + EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, &attr), + PosixErrorIs(EINVAL)); + + // Names should be shorter than NAME_MAX. + char max[NAME_MAX + 3]; + max[0] = '/'; + for (size_t i = 1; i < NAME_MAX + 2; i++) { + max[i] = 'a'; + } + max[NAME_MAX + 2] = '\0'; + + EXPECT_THAT(MqOpen(std::string(max), O_RDWR | O_CREAT | O_EXCL, 0777, &attr), + PosixErrorIs(ENAMETOOLONG)); +} + +// Test creating a queue that already exists. +TEST(MqTest, CreateAlreadyExists) { + GTEST_SKIP(); + + PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( + MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr)); + + EXPECT_THAT(MqOpen(queue.name(), O_RDWR | O_CREAT | O_EXCL, 0777, nullptr), + PosixErrorIs(EEXIST)); +} + +// Test opening a queue that doesn't exists. +TEST(MqTest, NoQueueExists) { + GTEST_SKIP(); + + // Choose a name to pass that's unlikely to exist if the test is run locally. + EXPECT_THAT(MqOpen("/gvisor-mq-test-nonexistent-queue", O_RDWR), + PosixErrorIs(ENOENT)); +} + +// Test trying to re-open a queue with invalid permissions. +TEST(MqTest, OpenNoAccess) { + SKIP_IF(IsRunningWithVFS1()); + + PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( + MqOpen(O_RDWR | O_CREAT | O_EXCL, 0000, nullptr)); + + EXPECT_THAT(MqOpen(queue.name(), O_RDONLY), PosixErrorIs(EACCES)); + EXPECT_THAT(MqOpen(queue.name(), O_WRONLY), PosixErrorIs(EACCES)); + EXPECT_THAT(MqOpen(queue.name(), O_RDWR), PosixErrorIs(EACCES)); +} + +// Test trying to re-open a read-only queue for write. +TEST(MqTest, OpenReadAccess) { + SKIP_IF(IsRunningWithVFS1()); + + PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( + MqOpen(O_RDWR | O_CREAT | O_EXCL, 0400, nullptr)); + + EXPECT_THAT(MqOpen(queue.name(), O_WRONLY), PosixErrorIs(EACCES)); + EXPECT_NO_ERRNO(MqOpen(queue.name(), O_RDONLY)); + queue.release(); +} + +// Test trying to re-open a write-only queue for read. +TEST(MqTest, OpenWriteAccess) { + SKIP_IF(IsRunningWithVFS1()); + + PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE( + MqOpen(O_RDWR | O_CREAT | O_EXCL, 0200, nullptr)); + + EXPECT_THAT(MqOpen(queue.name(), O_RDONLY), PosixErrorIs(EACCES)); + EXPECT_NO_ERRNO(MqOpen(queue.name(), O_WRONLY)); + queue.release(); +} + +} // namespace +} // namespace testing +} // namespace gvisor diff --git a/test/util/temp_path.cc b/test/util/temp_path.cc index e1bdee7fd..11399f372 100644 --- a/test/util/temp_path.cc +++ b/test/util/temp_path.cc @@ -34,20 +34,6 @@ namespace { std::atomic<uint64_t> global_temp_file_number = ATOMIC_VAR_INIT(1); -// Return a new temp filename, intended to be unique system-wide. -// -// The global file number helps maintain file naming consistency across -// different runs of a test. -// -// The timestamp is necessary because the test infrastructure invokes each -// test case in a separate process (resetting global_temp_file_number) and -// potentially in parallel, which allows for races between selecting and using a -// name. -std::string NextTempBasename() { - return absl::StrCat("gvisor_test_temp_", global_temp_file_number++, "_", - absl::ToUnixNanos(absl::Now())); -} - void TryDeleteRecursively(std::string const& path) { if (!path.empty()) { int undeleted_dirs = 0; @@ -85,6 +71,18 @@ std::string GetAbsoluteTestTmpdir() { return MakeAbsolute(tmp_dir, "").ValueOrDie(); } +// The global file number helps maintain file naming consistency across +// different runs of a test. +// +// The timestamp is necessary because the test infrastructure invokes each +// test case in a separate process (resetting global_temp_file_number) and +// potentially in parallel, which allows for races between selecting and using a +// name. +std::string NextTempBasename() { + return absl::StrCat("gvisor_test_temp_", global_temp_file_number++, "_", + absl::ToUnixNanos(absl::Now())); +} + PosixErrorOr<TempPath> TempPath::CreateFileWith(absl::string_view const parent, absl::string_view const content, mode_t const mode) { diff --git a/test/util/temp_path.h b/test/util/temp_path.h index 9e5ac11f4..6c8900b6b 100644 --- a/test/util/temp_path.h +++ b/test/util/temp_path.h @@ -27,6 +27,9 @@ namespace gvisor { namespace testing { +// Return a new temp filename, intended to be unique system-wide. +std::string NextTempBasename(); + // Returns an absolute path for a file in `dir` that does not yet exist. // Distinct calls to NewTempAbsPathInDir from the same process, even from // multiple threads, are guaranteed to return different paths. Distinct calls to |