summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorZyad A. Ali <zyad.ali.me@gmail.com>2021-07-15 15:23:28 +0200
committerZyad A. Ali <zyad.ali.me@gmail.com>2021-09-15 21:56:35 +0200
commitfc8819f43cae94db8345060f0af00e013dc86098 (patch)
treeed3585202342ebfa699c54f19fc1e8db72272af3 /test
parent149ca009678edc580de9f0b1d54f551d376742cb (diff)
Test creation and deletion of POSIX message queues.
Updates #136
Diffstat (limited to 'test')
-rw-r--r--test/syscalls/BUILD4
-rw-r--r--test/syscalls/linux/BUILD16
-rw-r--r--test/syscalls/linux/mq.cc234
-rw-r--r--test/util/temp_path.cc26
-rw-r--r--test/util/temp_path.h3
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