mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
2df29e11dd
This test flakes with <1% rate. There is a relevant comment explaining that "timestamps only have a resolution of seconds" and so we need to sleep for a second to actually see the timestamps change. However, currently the tests sleeps *before* calculating local time. Which beats the point. Move the local time declaration before the sleep. absl::ToTimeT() rounds down to the nearest second, discarding nanoseconds field completely. I think the host kernel might be rounding up the `msqid_ds` time fields. Tested: 1000 runs of this test pass natively. PiperOrigin-RevId: 455187650
887 lines
28 KiB
C++
887 lines
28 KiB
C++
// 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 <errno.h>
|
|
#include <signal.h>
|
|
#include <sys/ipc.h>
|
|
#include <sys/msg.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "absl/synchronization/notification.h"
|
|
#include "absl/time/clock.h"
|
|
#include "test/util/capability_util.h"
|
|
#include "test/util/signal_util.h"
|
|
#include "test/util/temp_path.h"
|
|
#include "test/util/test_util.h"
|
|
#include "test/util/thread_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
namespace {
|
|
|
|
// Source: include/uapi/linux/msg.h
|
|
constexpr int msgMnb = 16384; // Maximum number of bytes in a queue.
|
|
constexpr int msgMni = 32000; // Max number of identifiers.
|
|
constexpr int msgPool =
|
|
(msgMni * msgMnb / 1024); // Size of buffer pool used to hold message data.
|
|
constexpr int msgMap = msgMnb; // Maximum number of entries in message map.
|
|
constexpr int msgMax = 8192; // Maximum number of bytes in a single message.
|
|
constexpr int msgSsz = 16; // Message segment size.
|
|
constexpr int msgTql = msgMnb; // Maximum number of messages on all queues.
|
|
|
|
constexpr int kInterruptSignal = SIGALRM;
|
|
|
|
// Queue is a RAII class used to automatically clean message queues.
|
|
class Queue {
|
|
public:
|
|
explicit Queue(int id) : id_(id) {}
|
|
Queue(const Queue&) = delete;
|
|
Queue& operator=(const Queue&) = delete;
|
|
|
|
Queue(Queue&& other) { id_ = other.release(); }
|
|
|
|
~Queue() {
|
|
if (id_ >= 0) {
|
|
EXPECT_THAT(msgctl(id_, IPC_RMID, nullptr), SyscallSucceeds());
|
|
}
|
|
}
|
|
|
|
int release() {
|
|
int old = id_;
|
|
id_ = -1;
|
|
return old;
|
|
}
|
|
|
|
int get() { return id_; }
|
|
|
|
private:
|
|
int id_ = -1;
|
|
};
|
|
|
|
PosixErrorOr<Queue> Msgget(key_t key, int flags) {
|
|
int id = msgget(key, flags);
|
|
if (id == -1) {
|
|
return PosixError(errno, absl::StrFormat("msgget(%d, %d)", key, flags));
|
|
}
|
|
return Queue(id);
|
|
}
|
|
|
|
// Default size for messages.
|
|
constexpr size_t msgSize = 50;
|
|
|
|
// msgbuf is a simple buffer using to send and receive text messages for
|
|
// testing purposes.
|
|
struct msgbuf {
|
|
int64_t mtype;
|
|
char mtext[msgSize];
|
|
};
|
|
|
|
bool operator==(msgbuf& a, msgbuf& b) {
|
|
for (size_t i = 0; i < msgSize; i++) {
|
|
if (a.mtext[i] != b.mtext[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return a.mtype == b.mtype;
|
|
}
|
|
|
|
// msgmax represents a buffer for the largest possible single message.
|
|
struct msgmax {
|
|
int64_t mtype;
|
|
char mtext[msgMax];
|
|
};
|
|
|
|
// Test simple creation and retrieval for msgget(2).
|
|
TEST(MsgqueueTest, MsgGet) {
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
|
ASSERT_THAT(key, SyscallSucceeds());
|
|
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(key, IPC_CREAT));
|
|
EXPECT_THAT(msgget(key, 0), SyscallSucceedsWithValue(queue.get()));
|
|
}
|
|
|
|
// Test simple failure scenarios for msgget(2).
|
|
TEST(MsgqueueTest, MsgGetFail) {
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
|
ASSERT_THAT(key, SyscallSucceeds());
|
|
|
|
EXPECT_THAT(msgget(key, 0), SyscallFailsWithErrno(ENOENT));
|
|
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(key, IPC_CREAT));
|
|
EXPECT_THAT(msgget(key, IPC_CREAT | IPC_EXCL), SyscallFailsWithErrno(EEXIST));
|
|
}
|
|
|
|
// Test using msgget(2) with IPC_PRIVATE option.
|
|
TEST(MsgqueueTest, MsgGetIpcPrivate) {
|
|
Queue queue1 = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0));
|
|
Queue queue2 = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0));
|
|
EXPECT_NE(queue1.get(), queue2.get());
|
|
}
|
|
|
|
// Test simple msgsnd and msgrcv.
|
|
TEST(MsgqueueTest, MsgOpSimple) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, "A message."};
|
|
msgbuf rcv;
|
|
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0, 0),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
EXPECT_TRUE(buf == rcv);
|
|
}
|
|
|
|
// Test msgsnd and msgrcv of an empty message.
|
|
TEST(MsgqueueTest, MsgOpEmpty) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, ""};
|
|
msgbuf rcv;
|
|
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, 0, 0), SyscallSucceeds());
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0, 0),
|
|
SyscallSucceedsWithValue(0));
|
|
}
|
|
|
|
// Test truncation of message with MSG_NOERROR flag.
|
|
TEST(MsgqueueTest, MsgOpTruncate) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, ""};
|
|
msgbuf rcv;
|
|
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) - 1, 0, MSG_NOERROR),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext) - 1));
|
|
}
|
|
|
|
// Test msgsnd and msgrcv using invalid arguments.
|
|
TEST(MsgqueueTest, MsgOpInvalidArgs) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, ""};
|
|
|
|
EXPECT_THAT(msgsnd(-1, &buf, 0, 0), SyscallFailsWithErrno(EINVAL));
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, -1, 0), SyscallFailsWithErrno(EINVAL));
|
|
|
|
buf.mtype = -1;
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, 1, 0), SyscallFailsWithErrno(EINVAL));
|
|
|
|
EXPECT_THAT(msgrcv(-1, &buf, 1, 0, 0), SyscallFailsWithErrno(EINVAL));
|
|
EXPECT_THAT(msgrcv(queue.get(), &buf, -1, 0, 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
// Test non-blocking msgrcv with an empty queue.
|
|
TEST(MsgqueueTest, MsgOpNoMsg) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf rcv;
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(rcv.mtext) + 1, 0, IPC_NOWAIT),
|
|
SyscallFailsWithErrno(ENOMSG));
|
|
}
|
|
|
|
// Test non-blocking msgrcv with a non-empty queue, but no messages of wanted
|
|
// type.
|
|
TEST(MsgqueueTest, MsgOpNoMsgType) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, ""};
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &buf, sizeof(buf.mtext) + 1, 2, IPC_NOWAIT),
|
|
SyscallFailsWithErrno(ENOMSG));
|
|
}
|
|
|
|
// Test msgrcv with a larger size message than wanted, and truncation disabled.
|
|
TEST(MsgqueueTest, MsgOpTooBig) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, ""};
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &buf, sizeof(buf.mtext) - 1, 0, 0),
|
|
SyscallFailsWithErrno(E2BIG));
|
|
}
|
|
|
|
// Test receiving messages based on type.
|
|
TEST(MsgqueueTest, MsgRcvType) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
// Send messages in an order and receive them in reverse, based on type,
|
|
// which shouldn't block.
|
|
std::map<int64_t, msgbuf> typeToBuf = {
|
|
{1, msgbuf{1, "Message 1."}}, {2, msgbuf{2, "Message 2."}},
|
|
{3, msgbuf{3, "Message 3."}}, {4, msgbuf{4, "Message 4."}},
|
|
{5, msgbuf{5, "Message 5."}}, {6, msgbuf{6, "Message 6."}},
|
|
{7, msgbuf{7, "Message 7."}}, {8, msgbuf{8, "Message 8."}},
|
|
{9, msgbuf{9, "Message 9."}}};
|
|
|
|
for (auto const& [type, buf] : typeToBuf) {
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
for (int64_t i = typeToBuf.size(); i > 0; i--) {
|
|
msgbuf rcv;
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(typeToBuf[i].mtext) + 1, i, 0),
|
|
SyscallSucceedsWithValue(sizeof(typeToBuf[i].mtext)));
|
|
EXPECT_TRUE(typeToBuf[i] == rcv);
|
|
}
|
|
}
|
|
|
|
// Test using MSG_EXCEPT to receive a different-type message.
|
|
TEST(MsgqueueTest, MsgExcept) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
std::map<int64_t, msgbuf> typeToBuf = {
|
|
{1, msgbuf{1, "Message 1."}},
|
|
{2, msgbuf{2, "Message 2."}},
|
|
};
|
|
|
|
for (auto const& [type, buf] : typeToBuf) {
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
for (int64_t i = typeToBuf.size(); i > 0; i--) {
|
|
msgbuf actual = typeToBuf[i == 1 ? 2 : 1];
|
|
msgbuf rcv;
|
|
|
|
EXPECT_THAT(
|
|
msgrcv(queue.get(), &rcv, sizeof(actual.mtext) + 1, i, MSG_EXCEPT),
|
|
SyscallSucceedsWithValue(sizeof(actual.mtext)));
|
|
EXPECT_TRUE(actual == rcv);
|
|
}
|
|
}
|
|
|
|
// Test msgrcv with a negative type.
|
|
TEST(MsgqueueTest, MsgRcvTypeNegative) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
// When msgtyp is negative, msgrcv returns the first message with mtype less
|
|
// than or equal to the absolute value.
|
|
msgbuf buf{2, "A message."};
|
|
msgbuf rcv;
|
|
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
// Nothing is less than or equal to 1.
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) + 1, -1, IPC_NOWAIT),
|
|
SyscallFailsWithErrno(ENOMSG));
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) + 1, -3, 0),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
EXPECT_TRUE(buf == rcv);
|
|
}
|
|
|
|
// Test permission-related failure scenarios.
|
|
TEST(MsgqueueTest, MsgOpPermissions) {
|
|
AutoCapability cap(CAP_IPC_OWNER, false);
|
|
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0000));
|
|
|
|
msgbuf buf{1, ""};
|
|
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallFailsWithErrno(EACCES));
|
|
EXPECT_THAT(msgrcv(queue.get(), &buf, sizeof(buf.mtext), 0, 0),
|
|
SyscallFailsWithErrno(EACCES));
|
|
}
|
|
|
|
// Test limits for messages and queues.
|
|
TEST(MsgqueueTest, MsgOpLimits) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
msgbuf buf{1, "A message."};
|
|
|
|
// Limit for one message.
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, msgMax + 1, 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
// Limit for queue.
|
|
msgmax limit{1, ""};
|
|
for (size_t i = 0, msgCount = msgMnb / msgMax; i < msgCount; i++) {
|
|
EXPECT_THAT(msgsnd(queue.get(), &limit, sizeof(limit.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
EXPECT_THAT(msgsnd(queue.get(), &limit, sizeof(limit.mtext), IPC_NOWAIT),
|
|
SyscallFailsWithErrno(EAGAIN));
|
|
}
|
|
|
|
// MsgCopySupported returns true if MSG_COPY is supported.
|
|
bool MsgCopySupported() {
|
|
// msgrcv(2) man page states that MSG_COPY flag is available only if the
|
|
// kernel was built with the CONFIG_CHECKPOINT_RESTORE option. If MSG_COPY
|
|
// is used when the kernel was configured without the option, msgrcv produces
|
|
// a ENOSYS error.
|
|
// To avoid test failure, we perform a small test using msgrcv, and skip the
|
|
// test if errno == ENOSYS. This means that the test will always run on
|
|
// gVisor, but may be skipped on native linux.
|
|
|
|
auto maybe_id = Msgget(IPC_PRIVATE, 0600);
|
|
if (!maybe_id.ok()) {
|
|
return false;
|
|
}
|
|
Queue queue(std::move(maybe_id.ValueOrDie()));
|
|
msgbuf buf{1, "Test message."};
|
|
|
|
msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0);
|
|
return !(msgrcv(queue.get(), &buf, sizeof(buf.mtext) + 1, 0,
|
|
MSG_COPY | IPC_NOWAIT) == -1 &&
|
|
errno == ENOSYS);
|
|
}
|
|
|
|
// Test msgrcv using MSG_COPY.
|
|
TEST(MsgqueueTest, MsgCopy) {
|
|
SKIP_IF(!MsgCopySupported());
|
|
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgbuf bufs[5] = {
|
|
msgbuf{1, "Message 1."}, msgbuf{2, "Message 2."}, msgbuf{3, "Message 3."},
|
|
msgbuf{4, "Message 4."}, msgbuf{5, "Message 5."},
|
|
};
|
|
|
|
for (auto& buf : bufs) {
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Receive a copy of the messages.
|
|
for (size_t i = 0, size = sizeof(bufs) / sizeof(bufs[0]); i < size; i++) {
|
|
msgbuf buf = bufs[i];
|
|
msgbuf rcv;
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) + 1, i,
|
|
MSG_COPY | IPC_NOWAIT),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
EXPECT_TRUE(buf == rcv);
|
|
}
|
|
|
|
// Re-receive the messages normally.
|
|
for (auto& buf : bufs) {
|
|
msgbuf rcv;
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0, 0),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
EXPECT_TRUE(buf == rcv);
|
|
}
|
|
}
|
|
|
|
// Test msgrcv using MSG_COPY with invalid arguments.
|
|
TEST(MsgqueueTest, MsgCopyInvalidArgs) {
|
|
SKIP_IF(!MsgCopySupported());
|
|
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgbuf rcv;
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, 1, MSG_COPY),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
EXPECT_THAT(
|
|
msgrcv(queue.get(), &rcv, msgSize, 5, MSG_COPY | MSG_EXCEPT | IPC_NOWAIT),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
// Test msgrcv using MSG_COPY with invalid indices.
|
|
TEST(MsgqueueTest, MsgCopyInvalidIndex) {
|
|
SKIP_IF(!MsgCopySupported());
|
|
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgbuf rcv;
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, -3, MSG_COPY | IPC_NOWAIT),
|
|
SyscallFailsWithErrno(ENOMSG));
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, 5, MSG_COPY | IPC_NOWAIT),
|
|
SyscallFailsWithErrno(ENOMSG));
|
|
}
|
|
|
|
// Test msgrcv (most probably) blocking on an empty queue.
|
|
TEST(MsgqueueTest, MsgRcvBlocking) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgbuf buf{1, "A message."};
|
|
|
|
ScopedThread t([&] {
|
|
msgbuf rcv;
|
|
ASSERT_THAT(
|
|
RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0, 0),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
EXPECT_TRUE(rcv == buf);
|
|
});
|
|
|
|
// Sleep to try and make msgrcv block before sending a message.
|
|
absl::SleepFor(absl::Milliseconds(150));
|
|
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Test msgrcv (most probably) waiting for a specific-type message.
|
|
TEST(MsgqueueTest, MsgRcvTypeBlocking) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgbuf bufs[5] = {{1, "A message."},
|
|
{1, "A message."},
|
|
{1, "A message."},
|
|
{1, "A message."},
|
|
{2, "A different message."}};
|
|
|
|
ScopedThread t([&] {
|
|
msgbuf buf = bufs[4]; // Buffer that should be received.
|
|
msgbuf rcv;
|
|
ASSERT_THAT(
|
|
RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 2, 0),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
EXPECT_TRUE(rcv == buf);
|
|
});
|
|
|
|
// Sleep to try and make msgrcv block before sending messages.
|
|
absl::SleepFor(absl::Milliseconds(150));
|
|
|
|
// Send all buffers in order, only last one should be received.
|
|
for (auto& buf : bufs) {
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
}
|
|
|
|
// Test msgsnd (most probably) blocking on a full queue.
|
|
TEST(MsgqueueTest, MsgSndBlocking) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgmax buf{1, ""}; // Has max amount of bytes.
|
|
|
|
const size_t msgCount = msgMnb / msgMax; // Number of messages that can be
|
|
// sent without blocking.
|
|
|
|
ScopedThread t([&] {
|
|
// Fill the queue.
|
|
for (size_t i = 0; i < msgCount; i++) {
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Next msgsnd should block.
|
|
ASSERT_THAT(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
});
|
|
|
|
const DisableSave ds; // Too many syscalls.
|
|
|
|
// To increase the chance of the last msgsnd blocking before doing a msgrcv,
|
|
// we use MSG_COPY option to copy the last index in the queue. As long as
|
|
// MSG_COPY fails, the queue hasn't yet been filled. When MSG_COPY succeeds,
|
|
// the queue is filled, and most probably, a blocking msgsnd has been made.
|
|
msgmax rcv;
|
|
while (msgrcv(queue.get(), &rcv, msgMax, msgCount - 1,
|
|
MSG_COPY | IPC_NOWAIT) == -1 &&
|
|
errno == ENOMSG) {
|
|
}
|
|
|
|
// Delay a bit more for the blocking msgsnd.
|
|
absl::SleepFor(absl::Milliseconds(100));
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext), 0, 0),
|
|
SyscallSucceedsWithValue(sizeof(buf.mtext)));
|
|
}
|
|
|
|
// Test removing a queue while a blocking msgsnd is executing.
|
|
TEST(MsgqueueTest, MsgSndRmWhileBlocking) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
// Number of messages that can be sent without blocking.
|
|
const size_t msgCount = msgMnb / msgMax;
|
|
|
|
ScopedThread t([&] {
|
|
// Fill the queue.
|
|
msgmax buf{1, ""};
|
|
for (size_t i = 0; i < msgCount; i++) {
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Next msgsnd should block. Because we're repeating on EINTR, msgsnd may
|
|
// race with msgctl(IPC_RMID) and return EINVAL.
|
|
EXPECT_THAT(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallFails());
|
|
EXPECT_TRUE((errno == EIDRM || errno == EINVAL));
|
|
});
|
|
|
|
const DisableSave ds; // Too many syscalls.
|
|
|
|
// Similar to MsgSndBlocking, we do this to increase the chance of msgsnd
|
|
// blocking before removing the queue.
|
|
msgmax rcv;
|
|
while (msgrcv(queue.get(), &rcv, msgMax, msgCount - 1,
|
|
MSG_COPY | IPC_NOWAIT) == -1 &&
|
|
errno == ENOMSG) {
|
|
}
|
|
absl::SleepFor(absl::Milliseconds(100));
|
|
|
|
EXPECT_THAT(msgctl(queue.release(), IPC_RMID, nullptr), SyscallSucceeds());
|
|
}
|
|
|
|
// Test removing a queue while a blocking msgrcv is executing.
|
|
TEST(MsgqueueTest, MsgRcvRmWhileBlocking) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
ScopedThread t([&] {
|
|
// Because we're repeating on EINTR, msgsnd may race with msgctl(IPC_RMID)
|
|
// and return EINVAL.
|
|
msgbuf rcv;
|
|
EXPECT_THAT(RetryEINTR(msgrcv)(queue.get(), &rcv, 1, 2, 0), SyscallFails());
|
|
EXPECT_TRUE(errno == EIDRM || errno == EINVAL);
|
|
});
|
|
|
|
// Sleep to try and make msgrcv block before sending messages.
|
|
absl::SleepFor(absl::Milliseconds(150));
|
|
|
|
EXPECT_THAT(msgctl(queue.release(), IPC_RMID, nullptr), SyscallSucceeds());
|
|
}
|
|
|
|
// Test a collection of msgsnd/msgrcv operations in different processes.
|
|
TEST(MsgqueueTest, MsgOpGeneral) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
|
|
// Create multiple sending/receiving threads that send messages back and
|
|
// forth. There's a matching recv for each send, so by the end of the test,
|
|
// all threads should succeed and return.
|
|
const std::vector<msgbuf> msgs = {
|
|
msgbuf{1, "Message 1."}, msgbuf{2, "Message 2."}, msgbuf{3, "Message 3."},
|
|
msgbuf{4, "Message 4."}, msgbuf{5, "Message 5."}};
|
|
|
|
auto receiver = [&](int i) {
|
|
return [i, &msgs, &queue]() {
|
|
const msgbuf& target = msgs[i];
|
|
msgbuf rcv;
|
|
EXPECT_THAT(RetryEINTR(msgrcv)(queue.get(), &rcv,
|
|
sizeof(target.mtext) + 1, target.mtype, 0),
|
|
SyscallSucceedsWithValue(sizeof(target.mtext)));
|
|
EXPECT_EQ(rcv.mtype, target.mtype);
|
|
EXPECT_EQ(0, memcmp(rcv.mtext, target.mtext, sizeof(target.mtext)));
|
|
};
|
|
};
|
|
|
|
ScopedThread r1(receiver(0));
|
|
ScopedThread r2(receiver(1));
|
|
ScopedThread r3(receiver(2));
|
|
ScopedThread r4(receiver(3));
|
|
ScopedThread r5(receiver(4));
|
|
ScopedThread r6(receiver(0));
|
|
ScopedThread r7(receiver(1));
|
|
ScopedThread r8(receiver(2));
|
|
ScopedThread r9(receiver(3));
|
|
ScopedThread r10(receiver(4));
|
|
|
|
auto sender = [&](int i) {
|
|
return [i, &msgs, &queue]() {
|
|
const msgbuf& target = msgs[i];
|
|
EXPECT_THAT(
|
|
RetryEINTR(msgsnd)(queue.get(), &target, sizeof(target.mtext), 0),
|
|
SyscallSucceeds());
|
|
};
|
|
};
|
|
|
|
ScopedThread s1(sender(0));
|
|
ScopedThread s2(sender(1));
|
|
ScopedThread s3(sender(2));
|
|
ScopedThread s4(sender(3));
|
|
ScopedThread s5(sender(4));
|
|
ScopedThread s6(sender(0));
|
|
ScopedThread s7(sender(1));
|
|
ScopedThread s8(sender(2));
|
|
ScopedThread s9(sender(3));
|
|
ScopedThread s10(sender(4));
|
|
}
|
|
|
|
void empty_sighandler(int sig, siginfo_t* info, void* context) {}
|
|
|
|
TEST(MsgqueueTest, InterruptRecv) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
char buf[64];
|
|
|
|
absl::Notification done, exit;
|
|
|
|
// Thread calling msgrcv with no corresponding send. It would block forever,
|
|
// but we'll interrupt with a signal below.
|
|
ScopedThread t([&] {
|
|
struct sigaction sa = {};
|
|
sa.sa_sigaction = empty_sighandler;
|
|
sigfillset(&sa.sa_mask);
|
|
sa.sa_flags = SA_SIGINFO;
|
|
auto cleanup_sigaction =
|
|
ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kInterruptSignal, sa));
|
|
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(
|
|
ScopedSignalMask(SIG_UNBLOCK, kInterruptSignal));
|
|
|
|
EXPECT_THAT(msgrcv(queue.get(), &buf, sizeof(buf), 0, 0),
|
|
SyscallFailsWithErrno(EINTR));
|
|
|
|
done.Notify();
|
|
exit.WaitForNotification();
|
|
});
|
|
|
|
const DisableSave ds; // Too many syscalls.
|
|
|
|
// We want the signal to arrive while msgrcv is blocking, but not after the
|
|
// thread has exited. Signals that arrive before msgrcv are no-ops.
|
|
do {
|
|
EXPECT_THAT(kill(getpid(), kInterruptSignal), SyscallSucceeds());
|
|
absl::SleepFor(absl::Milliseconds(100)); // Rate limit.
|
|
} while (!done.HasBeenNotified());
|
|
|
|
exit.Notify();
|
|
t.Join();
|
|
}
|
|
|
|
TEST(MsgqueueTest, InterruptSend) {
|
|
Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600));
|
|
msgmax buf{1, ""};
|
|
// Number of messages that can be sent without blocking.
|
|
const size_t msgCount = msgMnb / msgMax;
|
|
|
|
// Fill the queue.
|
|
for (size_t i = 0; i < msgCount; i++) {
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
absl::Notification done, exit;
|
|
|
|
// Thread calling msgsnd on a full queue. It would block forever, but we'll
|
|
// interrupt with a signal below.
|
|
ScopedThread t([&] {
|
|
struct sigaction sa = {};
|
|
sa.sa_sigaction = empty_sighandler;
|
|
sigfillset(&sa.sa_mask);
|
|
sa.sa_flags = SA_SIGINFO;
|
|
auto cleanup_sigaction =
|
|
ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kInterruptSignal, sa));
|
|
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(
|
|
ScopedSignalMask(SIG_UNBLOCK, kInterruptSignal));
|
|
|
|
EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallFailsWithErrno(EINTR));
|
|
|
|
done.Notify();
|
|
exit.WaitForNotification();
|
|
});
|
|
|
|
const DisableSave ds; // Too many syscalls.
|
|
|
|
// We want the signal to arrive while msgsnd is blocking, but not after the
|
|
// thread has exited. Signals that arrive before msgsnd are no-ops.
|
|
do {
|
|
EXPECT_THAT(kill(getpid(), kInterruptSignal), SyscallSucceeds());
|
|
absl::SleepFor(absl::Milliseconds(100)); // Rate limit.
|
|
} while (!done.HasBeenNotified());
|
|
|
|
exit.Notify();
|
|
t.Join();
|
|
}
|
|
|
|
// Test msgctl with IPC_STAT option.
|
|
TEST(MsgqueueTest, MsgCtlIpcStat) {
|
|
// The timestamps only have a resolution of seconds; slow down so we actually
|
|
// see the timestamps change.
|
|
auto start = absl::Now();
|
|
absl::SleepFor(absl::Milliseconds(1010));
|
|
|
|
Queue queue(msgget(IPC_PRIVATE, 0600));
|
|
ASSERT_THAT(queue.get(), SyscallSucceeds());
|
|
|
|
const uid_t uid = getuid();
|
|
const gid_t gid = getgid();
|
|
const pid_t pid = getpid();
|
|
|
|
struct msqid_ds ds;
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
|
|
EXPECT_EQ(ds.msg_perm.__key, IPC_PRIVATE);
|
|
EXPECT_EQ(ds.msg_perm.uid, uid);
|
|
EXPECT_EQ(ds.msg_perm.gid, gid);
|
|
EXPECT_EQ(ds.msg_perm.cuid, uid);
|
|
EXPECT_EQ(ds.msg_perm.cgid, gid);
|
|
EXPECT_EQ(ds.msg_perm.mode, 0600);
|
|
|
|
EXPECT_EQ(ds.msg_stime, 0);
|
|
EXPECT_EQ(ds.msg_rtime, 0);
|
|
EXPECT_GE(ds.msg_ctime, absl::ToTimeT(start));
|
|
|
|
EXPECT_EQ(ds.msg_cbytes, 0);
|
|
EXPECT_EQ(ds.msg_qnum, 0);
|
|
EXPECT_EQ(ds.msg_qbytes, msgMnb);
|
|
EXPECT_EQ(ds.msg_lspid, 0);
|
|
EXPECT_EQ(ds.msg_lrpid, 0);
|
|
|
|
auto pre_send = absl::Now();
|
|
absl::SleepFor(absl::Milliseconds(1010));
|
|
|
|
msgbuf buf{1, "A message."};
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
|
|
EXPECT_GE(ds.msg_stime, absl::ToTimeT(pre_send));
|
|
EXPECT_EQ(ds.msg_rtime, 0);
|
|
EXPECT_GE(ds.msg_ctime, absl::ToTimeT(start));
|
|
|
|
EXPECT_EQ(ds.msg_cbytes, msgSize);
|
|
EXPECT_EQ(ds.msg_qnum, 1);
|
|
EXPECT_EQ(ds.msg_qbytes, msgMnb);
|
|
EXPECT_EQ(ds.msg_lspid, pid);
|
|
EXPECT_EQ(ds.msg_lrpid, 0);
|
|
|
|
auto pre_receive = absl::Now();
|
|
absl::SleepFor(absl::Milliseconds(1010));
|
|
|
|
ASSERT_THAT(msgrcv(queue.get(), &buf, sizeof(buf.mtext), 0, 0),
|
|
SyscallSucceedsWithValue(msgSize));
|
|
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
|
|
EXPECT_GE(ds.msg_stime, absl::ToTimeT(pre_send));
|
|
EXPECT_GE(ds.msg_rtime, absl::ToTimeT(pre_receive));
|
|
EXPECT_GE(ds.msg_ctime, absl::ToTimeT(start));
|
|
|
|
EXPECT_EQ(ds.msg_cbytes, 0);
|
|
EXPECT_EQ(ds.msg_qnum, 0);
|
|
EXPECT_EQ(ds.msg_qbytes, msgMnb);
|
|
EXPECT_EQ(ds.msg_lspid, pid);
|
|
EXPECT_EQ(ds.msg_lrpid, pid);
|
|
}
|
|
|
|
// Test msgctl with IPC_STAT option on a write-only queue.
|
|
TEST(MsgqueueTest, MsgCtlIpcStatWriteOnly) {
|
|
// Drop CAP_IPC_OWNER which allows us to bypass permissions.
|
|
AutoCapability cap(CAP_IPC_OWNER, false);
|
|
|
|
Queue queue(msgget(IPC_PRIVATE, 0200));
|
|
ASSERT_THAT(queue.get(), SyscallSucceeds());
|
|
|
|
struct msqid_ds ds;
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds),
|
|
SyscallFailsWithErrno(EACCES));
|
|
}
|
|
|
|
// Test msgctl with IPC_SET option.
|
|
TEST(MsgqueueTest, MsgCtlIpcSet) {
|
|
Queue queue(msgget(IPC_PRIVATE, 0600));
|
|
ASSERT_THAT(queue.get(), SyscallSucceeds());
|
|
|
|
struct msqid_ds ds;
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
EXPECT_EQ(ds.msg_perm.mode, 0600);
|
|
|
|
ds.msg_perm.mode = 0777;
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_SET, &ds), SyscallSucceeds());
|
|
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
EXPECT_EQ(ds.msg_perm.mode, 0777);
|
|
}
|
|
|
|
// Test increasing msg_qbytes beyond limit with IPC_SET.
|
|
TEST(MsgqueueTest, MsgCtlIpcSetMaxBytes) {
|
|
// Drop CAP_SYS_RESOURCE which allows us to increase msg_qbytes beyond the
|
|
// system parameter MSGMNB.
|
|
AutoCapability cap(CAP_SYS_RESOURCE, false);
|
|
|
|
Queue queue(msgget(IPC_PRIVATE, 0600));
|
|
ASSERT_THAT(queue.get(), SyscallSucceeds());
|
|
|
|
struct msqid_ds ds;
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
EXPECT_EQ(ds.msg_qbytes, msgMnb);
|
|
|
|
ds.msg_qbytes = msgMnb - 10;
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_SET, &ds), SyscallSucceeds());
|
|
|
|
ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds());
|
|
EXPECT_EQ(ds.msg_qbytes, msgMnb - 10);
|
|
|
|
ds.msg_qbytes = msgMnb + 10;
|
|
EXPECT_THAT(msgctl(queue.get(), IPC_SET, &ds), SyscallFailsWithErrno(EPERM));
|
|
}
|
|
|
|
// Test msgctl with IPC_INFO option.
|
|
TEST(MsgqueueTest, MsgCtlIpcInfo) {
|
|
struct msginfo info;
|
|
ASSERT_THAT(msgctl(0, IPC_INFO, reinterpret_cast<struct msqid_ds*>(&info)),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_GT(info.msgmax, 0);
|
|
EXPECT_GT(info.msgmni, 0);
|
|
EXPECT_GT(info.msgmnb, 0);
|
|
EXPECT_EQ(info.msgpool, msgPool);
|
|
EXPECT_EQ(info.msgmap, msgMap);
|
|
EXPECT_EQ(info.msgssz, msgSsz);
|
|
EXPECT_EQ(info.msgtql, msgTql);
|
|
}
|
|
|
|
// Test msgctl with MSG_INFO option.
|
|
TEST(MsgqueueTest, MsgCtlMsgInfo) {
|
|
struct msginfo info;
|
|
ASSERT_THAT(msgctl(0, MSG_INFO, reinterpret_cast<struct msqid_ds*>(&info)),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_GT(info.msgmax, 0);
|
|
EXPECT_GT(info.msgmni, 0);
|
|
EXPECT_GT(info.msgmnb, 0);
|
|
EXPECT_EQ(info.msgpool, 0); // Number of queues in the system.
|
|
EXPECT_EQ(info.msgmap, 0); // Total number of messages in all queues.
|
|
EXPECT_EQ(info.msgtql, 0); // Total number of bytes in all messages.
|
|
EXPECT_EQ(info.msgssz, msgSsz);
|
|
|
|
// Add a queue and a message.
|
|
Queue queue(msgget(IPC_PRIVATE, 0600));
|
|
ASSERT_THAT(queue.get(), SyscallSucceeds());
|
|
|
|
msgbuf buf{1, "A message."};
|
|
ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0),
|
|
SyscallSucceeds());
|
|
|
|
ASSERT_THAT(msgctl(0, MSG_INFO, reinterpret_cast<struct msqid_ds*>(&info)),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_GT(info.msgmax, 0);
|
|
EXPECT_GT(info.msgmni, 0);
|
|
EXPECT_GT(info.msgmnb, 0);
|
|
EXPECT_EQ(info.msgpool, 1); // Number of queues in the system.
|
|
EXPECT_EQ(info.msgmap, 1); // Total number of messages in all queues.
|
|
EXPECT_EQ(info.msgtql, msgSize); // Total number of bytes in all messages.
|
|
EXPECT_EQ(info.msgssz, msgSsz);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace testing
|
|
} // namespace gvisor
|
|
|
|
int main(int argc, char** argv) {
|
|
// Some tests depend on delivering a signal to the main thread. Block the
|
|
// target signal so that any other threads created by TestInit will also have
|
|
// the signal blocked.
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, gvisor::testing::kInterruptSignal);
|
|
TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
|
|
|
|
gvisor::testing::TestInit(&argc, &argv);
|
|
return gvisor::testing::RunAllTests();
|
|
}
|