summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/e2e/integration_test.go46
-rw-r--r--test/perf/BUILD28
-rw-r--r--test/perf/linux/BUILD73
-rw-r--r--test/perf/linux/dup_benchmark.cc55
-rw-r--r--test/perf/linux/verity_open_benchmark.cc2
-rw-r--r--test/perf/linux/verity_open_read_close_benchmark.cc75
-rw-r--r--test/perf/linux/verity_randread_benchmark.cc108
-rw-r--r--test/perf/linux/verity_read_benchmark.cc69
-rw-r--r--test/root/chroot_test.go10
-rw-r--r--test/syscalls/linux/BUILD4
-rw-r--r--test/syscalls/linux/dup.cc41
-rw-r--r--test/syscalls/linux/lseek.cc3
-rw-r--r--test/syscalls/linux/memfd.cc5
-rw-r--r--test/syscalls/linux/msgqueue.cc572
-rw-r--r--test/syscalls/linux/prctl.cc6
-rw-r--r--test/syscalls/linux/proc.cc42
-rw-r--r--test/syscalls/linux/socket_netlink_route.cc116
-rw-r--r--test/syscalls/linux/stat.cc2
-rw-r--r--test/syscalls/linux/statfs.cc2
19 files changed, 1239 insertions, 20 deletions
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go
index 9e22c9a7d..d41139944 100644
--- a/test/e2e/integration_test.go
+++ b/test/e2e/integration_test.go
@@ -742,3 +742,49 @@ func TestUnmount(t *testing.T) {
t.Fatalf("docker run failed: %v", err)
}
}
+
+func TestDeleteInterface(t *testing.T) {
+ if testutil.IsRunningWithHostNet() {
+ t.Skip("not able to remove interfaces on hostnet")
+ }
+
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ opts := dockerutil.RunOpts{
+ Image: "basic/alpine",
+ CapAdd: []string{"NET_ADMIN"},
+ }
+ if err := d.Spawn(ctx, opts, "sleep", "1000"); err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ }
+
+ // We should be able to remove eth0.
+ output, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link del dev eth0")
+ if err != nil {
+ t.Fatalf("failed to remove eth0: %s, output: %s", err, output)
+ }
+ // Verify that eth0 is no longer there.
+ output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link show")
+ if err != nil {
+ t.Fatalf("docker exec ip link show failed: %s, output: %s", err, output)
+ }
+ if strings.Contains(output, "eth0") {
+ t.Fatalf("failed to remove eth0")
+ }
+
+ // Loopback device can't be removed.
+ output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link del dev lo")
+ if err == nil {
+ t.Fatalf("should not remove the loopback device: %v", output)
+ }
+ // Verify that lo is still there.
+ output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link show")
+ if err != nil {
+ t.Fatalf("docker exec ip link show failed: %s, output: %s", err, output)
+ }
+ if !strings.Contains(output, "lo") {
+ t.Fatalf("loopback interface is removed")
+ }
+}
diff --git a/test/perf/BUILD b/test/perf/BUILD
index 97ca0e75a..58fe333ee 100644
--- a/test/perf/BUILD
+++ b/test/perf/BUILD
@@ -70,6 +70,13 @@ syscall_test(
)
syscall_test(
+ size = "large",
+ add_overlay = True,
+ debug = False,
+ test = "//test/perf/linux:dup_benchmark",
+)
+
+syscall_test(
debug = False,
test = "//test/perf/linux:pipe_benchmark",
)
@@ -146,3 +153,24 @@ syscall_test(
test = "//test/perf/linux:verity_open_benchmark",
vfs1 = False,
)
+
+syscall_test(
+ size = "large",
+ debug = False,
+ test = "//test/perf/linux:verity_read_benchmark",
+ vfs1 = False,
+)
+
+syscall_test(
+ size = "large",
+ debug = False,
+ test = "//test/perf/linux:verity_randread_benchmark",
+ vfs1 = False,
+)
+
+syscall_test(
+ size = "large",
+ debug = False,
+ test = "//test/perf/linux:verity_open_read_close_benchmark",
+ vfs1 = False,
+)
diff --git a/test/perf/linux/BUILD b/test/perf/linux/BUILD
index b4f192227..61ed98ff5 100644
--- a/test/perf/linux/BUILD
+++ b/test/perf/linux/BUILD
@@ -109,6 +109,22 @@ cc_binary(
)
cc_binary(
+ name = "dup_benchmark",
+ testonly = 1,
+ srcs = [
+ "dup_benchmark.cc",
+ ],
+ deps = [
+ gbenchmark,
+ gtest,
+ "//test/util:fs_util",
+ "//test/util:logging",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ ],
+)
+
+cc_binary(
name = "read_benchmark",
testonly = 1,
srcs = [
@@ -389,3 +405,60 @@ cc_binary(
"//test/util:verity_util",
],
)
+
+cc_binary(
+ name = "verity_read_benchmark",
+ testonly = 1,
+ srcs = [
+ "verity_read_benchmark.cc",
+ ],
+ deps = [
+ gbenchmark,
+ gtest,
+ "//test/util:capability_util",
+ "//test/util:fs_util",
+ "//test/util:logging",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
+
+cc_binary(
+ name = "verity_randread_benchmark",
+ testonly = 1,
+ srcs = [
+ "verity_randread_benchmark.cc",
+ ],
+ deps = [
+ gbenchmark,
+ gtest,
+ "//test/util:capability_util",
+ "//test/util:fs_util",
+ "//test/util:logging",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
+
+cc_binary(
+ name = "verity_open_read_close_benchmark",
+ testonly = 1,
+ srcs = [
+ "verity_open_read_close_benchmark.cc",
+ ],
+ deps = [
+ gbenchmark,
+ gtest,
+ "//test/util:capability_util",
+ "//test/util:fs_util",
+ "//test/util:logging",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
diff --git a/test/perf/linux/dup_benchmark.cc b/test/perf/linux/dup_benchmark.cc
new file mode 100644
index 000000000..5d808d225
--- /dev/null
+++ b/test/perf/linux/dup_benchmark.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 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 <stdlib.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "benchmark/benchmark.h"
+#include "test/util/fs_util.h"
+#include "test/util/logging.h"
+#include "test/util/temp_path.h"
+
+namespace gvisor {
+
+namespace {
+
+void BM_Dup(benchmark::State& state) {
+ const int size = state.range(0);
+
+ for (auto _ : state) {
+ std::vector<int> v;
+ for (int i = 0; i < size; i++) {
+ int fd = dup(2);
+ TEST_CHECK(fd != -1);
+ v.push_back(fd);
+ }
+ for (int i = 0; i < size; i++) {
+ int fd = v[i];
+ close(fd);
+ }
+ }
+ state.SetItemsProcessed(state.iterations() * size);
+}
+
+BENCHMARK(BM_Dup)->Range(1, 1 << 15)->UseRealTime();
+
+} // namespace
+
+} // namespace gvisor
diff --git a/test/perf/linux/verity_open_benchmark.cc b/test/perf/linux/verity_open_benchmark.cc
index ce53a2100..026b6f101 100644
--- a/test/perf/linux/verity_open_benchmark.cc
+++ b/test/perf/linux/verity_open_benchmark.cc
@@ -36,8 +36,6 @@ namespace testing {
namespace {
void BM_Open(benchmark::State& state) {
- SKIP_IF(IsRunningWithVFS1());
-
const int size = state.range(0);
std::vector<TempPath> cache;
std::vector<EnableTarget> targets;
diff --git a/test/perf/linux/verity_open_read_close_benchmark.cc b/test/perf/linux/verity_open_read_close_benchmark.cc
new file mode 100644
index 000000000..e77577f22
--- /dev/null
+++ b/test/perf/linux/verity_open_read_close_benchmark.cc
@@ -0,0 +1,75 @@
+// 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 <stdlib.h>
+#include <sys/mount.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "benchmark/benchmark.h"
+#include "test/util/capability_util.h"
+#include "test/util/fs_util.h"
+#include "test/util/logging.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+void BM_VerityOpenReadClose(benchmark::State& state) {
+ const int size = state.range(0);
+
+ // Mount a tmpfs file system to be wrapped by a verity fs.
+ TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ TEST_CHECK(mount("", dir.path().c_str(), "tmpfs", 0, "") == 0);
+
+ std::vector<TempPath> cache;
+ std::vector<EnableTarget> targets;
+
+ for (int i = 0; i < size; i++) {
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(dir.path(), "some contents", 0644));
+ targets.emplace_back(
+ EnableTarget(std::string(Basename(file.path())), O_RDONLY));
+ cache.emplace_back(std::move(file));
+ }
+
+ std::string verity_dir =
+ TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(dir.path(), targets));
+
+ char buf[1];
+ unsigned int seed = 1;
+ for (auto _ : state) {
+ const int chosen = rand_r(&seed) % size;
+ int fd = open(JoinPath(verity_dir, targets[chosen].path).c_str(), O_RDONLY);
+ TEST_CHECK(fd != -1);
+ TEST_CHECK(read(fd, buf, 1) == 1);
+ close(fd);
+ }
+}
+
+BENCHMARK(BM_VerityOpenReadClose)->Range(1000, 16384)->UseRealTime();
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/perf/linux/verity_randread_benchmark.cc b/test/perf/linux/verity_randread_benchmark.cc
new file mode 100644
index 000000000..4178cfad8
--- /dev/null
+++ b/test/perf/linux/verity_randread_benchmark.cc
@@ -0,0 +1,108 @@
+// 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 <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "gtest/gtest.h"
+#include "benchmark/benchmark.h"
+#include "test/util/logging.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// Create a 1GB file that will be read from at random positions. This should
+// invalid any performance gains from caching.
+const uint64_t kFileSize = Megabytes(1024);
+
+// How many bytes to write at once to initialize the file used to read from.
+const uint32_t kWriteSize = 65536;
+
+// Largest benchmarked read unit.
+const uint32_t kMaxRead = Megabytes(64);
+
+// Global test state, initialized once per process lifetime.
+struct GlobalState {
+ explicit GlobalState() {
+ // Mount a tmpfs file system to be wrapped by a verity fs.
+ tmp_dir_ = TempPath::CreateDir().ValueOrDie();
+ TEST_CHECK(mount("", tmp_dir_.path().c_str(), "tmpfs", 0, "") == 0);
+ file_ = TempPath::CreateFileIn(tmp_dir_.path()).ValueOrDie();
+ filename_ = std::string(Basename(file_.path()));
+
+ FileDescriptor fd = Open(file_.path(), O_WRONLY).ValueOrDie();
+
+ // Try to minimize syscalls by using maximum size writev() requests.
+ std::vector<char> buffer(kWriteSize);
+ RandomizeBuffer(buffer.data(), buffer.size());
+ const std::vector<std::vector<struct iovec>> iovecs_list =
+ GenerateIovecs(kFileSize + kMaxRead, buffer.data(), buffer.size());
+ for (const auto& iovecs : iovecs_list) {
+ TEST_CHECK(writev(fd.get(), iovecs.data(), iovecs.size()) >= 0);
+ }
+ verity_dir_ =
+ MountVerity(tmp_dir_.path(), {EnableTarget(filename_, O_RDONLY)})
+ .ValueOrDie();
+ }
+ TempPath tmp_dir_;
+ TempPath file_;
+ std::string verity_dir_;
+ std::string filename_;
+};
+
+GlobalState& GetGlobalState() {
+ // This gets created only once throughout the lifetime of the process.
+ // Use a dynamically allocated object (that is never deleted) to avoid order
+ // of destruction of static storage variables issues.
+ static GlobalState* const state =
+ // The actual file size is the maximum random seek range (kFileSize) + the
+ // maximum read size so we can read that number of bytes at the end of the
+ // file.
+ new GlobalState();
+ return *state;
+}
+
+void BM_VerityRandRead(benchmark::State& state) {
+ const int size = state.range(0);
+
+ GlobalState& global_state = GetGlobalState();
+ FileDescriptor verity_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(
+ JoinPath(global_state.verity_dir_, global_state.filename_), O_RDONLY));
+ std::vector<char> buf(size);
+
+ unsigned int seed = 1;
+ for (auto _ : state) {
+ TEST_CHECK(PreadFd(verity_fd.get(), buf.data(), buf.size(),
+ rand_r(&seed) % kFileSize) == size);
+ }
+
+ state.SetBytesProcessed(static_cast<int64_t>(size) *
+ static_cast<int64_t>(state.iterations()));
+}
+
+BENCHMARK(BM_VerityRandRead)->Range(1, kMaxRead)->UseRealTime();
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/perf/linux/verity_read_benchmark.cc b/test/perf/linux/verity_read_benchmark.cc
new file mode 100644
index 000000000..738b5ba45
--- /dev/null
+++ b/test/perf/linux/verity_read_benchmark.cc
@@ -0,0 +1,69 @@
+// 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 <stdlib.h>
+#include <sys/mount.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "benchmark/benchmark.h"
+#include "test/util/capability_util.h"
+#include "test/util/fs_util.h"
+#include "test/util/logging.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+void BM_VerityRead(benchmark::State& state) {
+ const int size = state.range(0);
+ const std::string contents(size, 0);
+
+ // Mount a tmpfs file system to be wrapped by a verity fs.
+ TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ TEST_CHECK(mount("", dir.path().c_str(), "tmpfs", 0, "") == 0);
+
+ auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
+ dir.path(), contents, TempPath::kDefaultFileMode));
+ std::string filename = std::string(Basename(path.path()));
+
+ std::string verity_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(
+ MountVerity(dir.path(), {EnableTarget(filename, O_RDONLY)}));
+
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(JoinPath(verity_dir, filename), O_RDONLY));
+ std::vector<char> buf(size);
+ for (auto _ : state) {
+ TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), 0) == size);
+ }
+
+ state.SetBytesProcessed(static_cast<int64_t>(size) *
+ static_cast<int64_t>(state.iterations()));
+}
+
+BENCHMARK(BM_VerityRead)->Range(1, 1 << 26)->UseRealTime();
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/root/chroot_test.go b/test/root/chroot_test.go
index 58fcd6f08..5114a9602 100644
--- a/test/root/chroot_test.go
+++ b/test/root/chroot_test.go
@@ -68,13 +68,15 @@ func TestChroot(t *testing.T) {
if err != nil {
t.Fatalf("error listing %q: %v", chroot, err)
}
- if want, got := 1, len(fi); want != got {
+ if want, got := 2, len(fi); want != got {
t.Fatalf("chroot dir got %d entries, want %d", got, want)
}
- // chroot dir is prepared by runsc and should contains only /proc.
- if fi[0].Name() != "proc" {
- t.Errorf("chroot got children %v, want %v", fi[0].Name(), "proc")
+ // chroot dir is prepared by runsc and should contains only /etc and /proc.
+ for i, want := range []string{"etc", "proc"} {
+ if got := fi[i].Name(); got != want {
+ t.Errorf("chroot got child %v, want %v", got, want)
+ }
}
d.CleanUp(ctx)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 7185df076..7129a797b 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -560,6 +560,7 @@ cc_binary(
deps = [
"//test/util:eventfd_util",
"//test/util:file_descriptor",
+ "@com_google_absl//absl/memory",
gtest,
"//test/util:fs_util",
"//test/util:posix_error",
@@ -1750,6 +1751,7 @@ cc_binary(
"//test/util:mount_util",
"@com_google_absl//absl/container:node_hash_set",
"@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
gtest,
@@ -4170,9 +4172,11 @@ cc_binary(
srcs = ["msgqueue.cc"],
linkstatic = 1,
deps = [
+ "//test/util:capability_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "@com_google_absl//absl/time",
],
)
diff --git a/test/syscalls/linux/dup.cc b/test/syscalls/linux/dup.cc
index ba4e13fb9..8f0974f45 100644
--- a/test/syscalls/linux/dup.cc
+++ b/test/syscalls/linux/dup.cc
@@ -13,9 +13,11 @@
// limitations under the License.
#include <fcntl.h>
+#include <sys/resource.h>
#include <unistd.h>
#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
#include "test/util/eventfd_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
@@ -98,6 +100,45 @@ TEST(DupTest, Dup2) {
ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2));
}
+TEST(DupTest, Rlimit) {
+ auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
+
+ struct rlimit rl = {};
+ EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
+
+ ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
+
+ constexpr int kFDLimit = 101;
+ // Create a file descriptor that will be above the limit.
+ FileDescriptor aboveLimitFD =
+ ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, kFDLimit * 2 - 1));
+
+ rl.rlim_cur = kFDLimit;
+ ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
+ ASSERT_THAT(dup3(fd.get(), kFDLimit, 0), SyscallFails());
+
+ std::vector<std::unique_ptr<FileDescriptor>> fds;
+ int prev_fd = fd.get();
+ int used_fds = 0;
+ for (int i = 0; i < kFDLimit; ++i) {
+ int new_fd = dup(fd.get());
+ if (new_fd == -1) {
+ break;
+ }
+ auto f = absl::make_unique<FileDescriptor>(new_fd);
+ EXPECT_LT(new_fd, kFDLimit);
+ EXPECT_GT(new_fd, prev_fd);
+ // Check that all fds in (prev_fd, new_fd) are used.
+ for (int j = prev_fd + 1; j < new_fd; ++j) {
+ if (fcntl(j, F_GETFD) != -1) used_fds++;
+ }
+ prev_fd = new_fd;
+ fds.push_back(std::move(f));
+ }
+ EXPECT_EQ(fds.size() + used_fds, kFDLimit - fd.get() - 1);
+}
+
TEST(DupTest, Dup2SameFD) {
auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
diff --git a/test/syscalls/linux/lseek.cc b/test/syscalls/linux/lseek.cc
index d4f89527c..dbc21833f 100644
--- a/test/syscalls/linux/lseek.cc
+++ b/test/syscalls/linux/lseek.cc
@@ -121,7 +121,8 @@ TEST(LseekTest, InvalidFD) {
}
TEST(LseekTest, DirCurEnd) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY));
+ const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY));
ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
}
diff --git a/test/syscalls/linux/memfd.cc b/test/syscalls/linux/memfd.cc
index 4a450742b..dbd1c93ae 100644
--- a/test/syscalls/linux/memfd.cc
+++ b/test/syscalls/linux/memfd.cc
@@ -445,9 +445,10 @@ TEST(MemfdTest, SealsAreInodeLevelProperties) {
// Tmpfs files also support seals, but are created with F_SEAL_SEAL.
TEST(MemfdTest, TmpfsFilesHaveSealSeal) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp")));
+ std::string tmpdir = GetAbsoluteTestTmpdir();
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(tmpdir.c_str())));
const TempPath tmpfs_file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn("/tmp"));
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmpdir.c_str()));
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfs_file.path(), O_RDWR, 0644));
EXPECT_THAT(fcntl(fd.get(), F_GET_SEALS),
diff --git a/test/syscalls/linux/msgqueue.cc b/test/syscalls/linux/msgqueue.cc
index 2409de7e8..837e913d9 100644
--- a/test/syscalls/linux/msgqueue.cc
+++ b/test/syscalls/linux/msgqueue.cc
@@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
+#include "absl/time/clock.h"
+#include "test/util/capability_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -23,6 +26,10 @@ namespace gvisor {
namespace testing {
namespace {
+constexpr int msgMax = 8192; // Max size for message in bytes.
+constexpr int msgMni = 32000; // Max number of identifiers.
+constexpr int msgMnb = 16384; // Default max size of message queue in bytes.
+
// Queue is a RAII class used to automatically clean message queues.
class Queue {
public:
@@ -46,6 +53,25 @@ class Queue {
int id_ = -1;
};
+// 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;
+}
+
// Test simple creation and retrieval for msgget(2).
TEST(MsgqueueTest, MsgGet) {
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
@@ -82,6 +108,552 @@ TEST(MsgqueueTest, MsgGetIpcPrivate) {
EXPECT_NE(queue1.get(), queue2.get());
}
+// Test simple msgsnd and msgrcv.
+TEST(MsgqueueTest, MsgOpSimple) {
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ // 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ // 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(msgget(IPC_PRIVATE, 0000));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ msgbuf buf{1, "A message."};
+
+ // Limit for one message.
+ EXPECT_THAT(msgsnd(queue.get(), &buf, msgMax + 1, 0),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Limit for queue.
+ // Use a buffer with the maximum mount of bytes that can be transformed to
+ // make it easier to exhaust the queue limit.
+ struct msgmax {
+ int64_t mtype;
+ char mtext[msgMax];
+ };
+
+ 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.
+
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ 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(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ msgbuf buf{1, "A message."};
+
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ msgbuf rcv;
+ TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0,
+ 0) == sizeof(buf.mtext) &&
+ buf == rcv);
+ _exit(0);
+ }
+
+ // 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());
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+// Test msgrcv (most probably) waiting for a specific-type message.
+TEST(MsgqueueTest, MsgRcvTypeBlocking) {
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ msgbuf bufs[5] = {{1, "A message."},
+ {1, "A message."},
+ {1, "A message."},
+ {1, "A message."},
+ {2, "A different message."}};
+
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ msgbuf buf = bufs[4]; // Buffer that should be received.
+ msgbuf rcv;
+ TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 2,
+ 0) == sizeof(buf.mtext) &&
+ buf == rcv);
+ _exit(0);
+ }
+
+ // 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());
+ }
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+// Test msgsnd (most probably) blocking on a full queue.
+TEST(MsgqueueTest, MsgSndBlocking) {
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ // Use a buffer with the maximum mount of bytes that can be transformed to
+ // make it easier to exhaust the queue limit.
+ struct msgmax {
+ int64_t mtype;
+ char mtext[msgMax];
+ };
+
+ msgmax buf{1, ""}; // Has max amount of bytes.
+
+ const size_t msgCount = msgMnb / msgMax; // Number of messages that can be
+ // sent without blocking.
+
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // Fill the queue.
+ for (size_t i = 0; i < msgCount; i++) {
+ TEST_PCHECK(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0) == 0);
+ }
+
+ // Next msgsnd should block.
+ TEST_PCHECK(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0) ==
+ 0);
+ _exit(0);
+ }
+
+ // 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)));
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+// Test removing a queue while a blocking msgsnd is executing.
+TEST(MsgqueueTest, MsgSndRmWhileBlocking) {
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ // Use a buffer with the maximum mount of bytes that can be transformed to
+ // make it easier to exhaust the queue limit.
+ struct msgmax {
+ int64_t mtype;
+ char mtext[msgMax];
+ };
+
+ const size_t msgCount = msgMnb / msgMax; // Number of messages that can be
+ // sent without blocking.
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // 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.
+ TEST_PCHECK(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0) ==
+ -1 &&
+ (errno == EIDRM || errno == EINVAL));
+ _exit(0);
+ }
+
+ // 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());
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+// Test removing a queue while a blocking msgrcv is executing.
+TEST(MsgqueueTest, MsgRcvRmWhileBlocking) {
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // Because we're repeating on EINTR, msgsnd may race with msgctl(IPC_RMID)
+ // and return EINVAL.
+ msgbuf rcv;
+ TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, 1, 2, 0) == -1 &&
+ (errno == EIDRM || errno == EINVAL));
+ _exit(0);
+ }
+
+ // Sleep to try and make msgrcv block before sending messages.
+ absl::SleepFor(absl::Milliseconds(150));
+
+ EXPECT_THAT(msgctl(queue.release(), IPC_RMID, nullptr), SyscallSucceeds());
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+// Test a collection of msgsnd/msgrcv operations in different processes.
+TEST(MsgqueueTest, MsgOpGeneral) {
+ Queue queue(msgget(IPC_PRIVATE, 0600));
+ ASSERT_THAT(queue.get(), SyscallSucceeds());
+
+ // Create 50 sending, and 50 receiving processes. There are only 5 messages to
+ // be sent and received, each with a different type. All messages will be sent
+ // and received equally (10 of each.) By the end of the test all processes
+ // should unblock and return normally.
+ const size_t msgCount = 5;
+ 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."}}};
+
+ std::vector<pid_t> children;
+
+ const size_t pCount = 50;
+ for (size_t i = 1; i <= pCount; i++) {
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ msgbuf buf = typeToBuf[(i % msgCount) + 1];
+ msgbuf rcv;
+ TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1,
+ (i % msgCount) + 1,
+ 0) == sizeof(buf.mtext) &&
+ buf == rcv);
+ _exit(0);
+ }
+ children.push_back(child_pid);
+ }
+
+ for (size_t i = 1; i <= pCount; i++) {
+ const pid_t child_pid = fork();
+ if (child_pid == 0) {
+ msgbuf buf = typeToBuf[(i % msgCount) + 1];
+ TEST_PCHECK(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0) ==
+ 0);
+ _exit(0);
+ }
+ children.push_back(child_pid);
+ }
+
+ for (auto const& pid : children) {
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
+ SyscallSucceedsWithValue(pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ }
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc
index 25b0e63d4..286b3d168 100644
--- a/test/syscalls/linux/prctl.cc
+++ b/test/syscalls/linux/prctl.cc
@@ -214,6 +214,12 @@ TEST(PrctlTest, RootDumpability) {
SyscallFailsWithErrno(EINVAL));
}
+TEST(PrctlTest, SetGetSubreaper) {
+ // Setting subreaper on PID 1 works vacuously because PID 1 is always a
+ // subreaper.
+ EXPECT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 1), SyscallSucceeds());
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index 78aa73edc..8a4025fed 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -54,6 +54,8 @@
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
@@ -88,6 +90,7 @@ using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsSupersetOf;
using ::testing::Pair;
+using ::testing::StartsWith;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
@@ -1622,10 +1625,41 @@ TEST(ProcPidStatusTest, HasBasicFields) {
ASSERT_FALSE(status_str.empty());
const auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(status_str));
- EXPECT_THAT(status, IsSupersetOf({Pair("Name", thread_name),
- Pair("Tgid", absl::StrCat(tgid)),
- Pair("Pid", absl::StrCat(tid)),
- Pair("PPid", absl::StrCat(getppid()))}));
+ EXPECT_THAT(status, IsSupersetOf({
+ Pair("Name", thread_name),
+ Pair("Tgid", absl::StrCat(tgid)),
+ Pair("Pid", absl::StrCat(tid)),
+ Pair("PPid", absl::StrCat(getppid())),
+ }));
+
+ if (!IsRunningWithVFS1()) {
+ uid_t ruid, euid, suid;
+ ASSERT_THAT(getresuid(&ruid, &euid, &suid), SyscallSucceeds());
+ gid_t rgid, egid, sgid;
+ ASSERT_THAT(getresgid(&rgid, &egid, &sgid), SyscallSucceeds());
+ std::vector<gid_t> supplementary_gids;
+ int ngids = getgroups(0, nullptr);
+ supplementary_gids.resize(ngids);
+ ASSERT_THAT(getgroups(ngids, supplementary_gids.data()),
+ SyscallSucceeds());
+
+ EXPECT_THAT(
+ status,
+ IsSupersetOf(std::vector<
+ ::testing::Matcher<std::pair<std::string, std::string>>>{
+ // gVisor doesn't support fsuid/gid, and even if it did there is
+ // no getfsuid/getfsgid().
+ Pair("Uid", StartsWith(absl::StrFormat("%d\t%d\t%d\t", ruid, euid,
+ suid))),
+ Pair("Gid", StartsWith(absl::StrFormat("%d\t%d\t%d\t", rgid, egid,
+ sgid))),
+ // ParseProcStatus strips leading whitespace for each value,
+ // so if the Groups line is empty then the trailing space is
+ // stripped.
+ Pair("Groups",
+ StartsWith(absl::StrJoin(supplementary_gids, " "))),
+ }));
+ }
});
}
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
index a5c788346..d5e1ce0cc 100644
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ b/test/syscalls/linux/socket_netlink_route.cc
@@ -44,6 +44,7 @@ namespace {
constexpr uint32_t kSeq = 12345;
+using ::testing::_;
using ::testing::AnyOf;
using ::testing::Eq;
@@ -244,7 +245,7 @@ TEST(NetlinkRouteTest, GetLinkByIndexNotFound) {
req.ifm.ifi_index = 1234590;
EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
- PosixErrorIs(ENODEV, ::testing::_));
+ PosixErrorIs(ENODEV, _));
}
TEST(NetlinkRouteTest, GetLinkByNameNotFound) {
@@ -273,7 +274,112 @@ TEST(NetlinkRouteTest, GetLinkByNameNotFound) {
NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len);
EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
- PosixErrorIs(ENODEV, ::testing::_));
+ PosixErrorIs(ENODEV, _));
+}
+
+TEST(NetlinkRouteTest, RemoveLoopbackByName) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
+
+ struct request {
+ struct nlmsghdr hdr;
+ struct ifinfomsg ifm;
+ struct rtattr rtattr;
+ char ifname[IFNAMSIZ];
+ char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
+ };
+
+ struct request req = {};
+ req.hdr.nlmsg_type = RTM_DELLINK;
+ req.hdr.nlmsg_flags = NLM_F_REQUEST;
+ req.hdr.nlmsg_seq = kSeq;
+ req.ifm.ifi_family = AF_UNSPEC;
+ req.rtattr.rta_type = IFLA_IFNAME;
+ req.rtattr.rta_len = RTA_LENGTH(loopback_link.name.size() + 1);
+ strncpy(req.ifname, loopback_link.name.c_str(), sizeof(req.ifname));
+ req.hdr.nlmsg_len =
+ NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len);
+
+ EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
+ PosixErrorIs(ENOTSUP, _));
+}
+
+TEST(NetlinkRouteTest, RemoveLoopbackByIndex) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
+
+ struct request {
+ struct nlmsghdr hdr;
+ struct ifinfomsg ifm;
+ };
+
+ struct request req = {};
+ req.hdr.nlmsg_len = sizeof(req);
+ req.hdr.nlmsg_type = RTM_DELLINK;
+ req.hdr.nlmsg_flags = NLM_F_REQUEST;
+ req.hdr.nlmsg_seq = kSeq;
+ req.ifm.ifi_family = AF_UNSPEC;
+ req.ifm.ifi_index = loopback_link.index;
+
+ EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
+ PosixErrorIs(ENOTSUP, _));
+}
+
+TEST(NetlinkRouteTest, RemoveLinkByIndexNotFound) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
+
+ struct request {
+ struct nlmsghdr hdr;
+ struct ifinfomsg ifm;
+ };
+
+ struct request req = {};
+ req.hdr.nlmsg_len = sizeof(req);
+ req.hdr.nlmsg_type = RTM_GETLINK;
+ req.hdr.nlmsg_flags = NLM_F_REQUEST;
+ req.hdr.nlmsg_seq = kSeq;
+ req.ifm.ifi_family = AF_UNSPEC;
+ req.ifm.ifi_index = 1234590;
+
+ EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
+ PosixErrorIs(ENODEV, _));
+}
+
+TEST(NetlinkRouteTest, RemoveLinkByNameNotFound) {
+ const std::string name = "nodevice?!";
+
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
+
+ struct request {
+ struct nlmsghdr hdr;
+ struct ifinfomsg ifm;
+ struct rtattr rtattr;
+ char ifname[IFNAMSIZ];
+ char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
+ };
+
+ struct request req = {};
+ req.hdr.nlmsg_type = RTM_DELLINK;
+ req.hdr.nlmsg_flags = NLM_F_REQUEST;
+ req.hdr.nlmsg_seq = kSeq;
+ req.ifm.ifi_family = AF_UNSPEC;
+ req.rtattr.rta_type = IFLA_IFNAME;
+ req.rtattr.rta_len = RTA_LENGTH(name.size() + 1);
+ strncpy(req.ifname, name.c_str(), sizeof(req.ifname));
+ req.hdr.nlmsg_len =
+ NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len);
+
+ EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
+ PosixErrorIs(ENODEV, _));
}
TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
@@ -295,7 +401,7 @@ TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
req.ifm.ifi_family = AF_UNSPEC;
EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
- PosixErrorIs(EOPNOTSUPP, ::testing::_));
+ PosixErrorIs(EOPNOTSUPP, _));
}
TEST(NetlinkRouteTest, MsgHdrMsgTrunc) {
@@ -536,7 +642,7 @@ TEST(NetlinkRouteTest, AddAndRemoveAddr) {
// Second delete should fail, as address no longer exists.
EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EADDRNOTAVAIL, ::testing::_));
+ PosixErrorIs(EADDRNOTAVAIL, _));
});
// Replace an existing address should succeed.
@@ -546,7 +652,7 @@ TEST(NetlinkRouteTest, AddAndRemoveAddr) {
// Create exclusive should fail, as we created the address above.
EXPECT_THAT(LinkAddExclusiveLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EEXIST, ::testing::_));
+ PosixErrorIs(EEXIST, _));
}
// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc
index 72f888659..19dc80d0c 100644
--- a/test/syscalls/linux/stat.cc
+++ b/test/syscalls/linux/stat.cc
@@ -765,7 +765,7 @@ TEST_F(StatTest, StatxSymlink) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
- std::string parent_dir = "/tmp";
+ std::string parent_dir = GetAbsoluteTestTmpdir();
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateSymlinkTo(parent_dir, test_file_name_));
std::string p = link.path();
diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc
index d4ea8e026..d057cdc09 100644
--- a/test/syscalls/linux/statfs.cc
+++ b/test/syscalls/linux/statfs.cc
@@ -28,7 +28,7 @@ namespace testing {
namespace {
TEST(StatfsTest, CannotStatBadPath) {
- auto temp_file = NewTempAbsPathInDir("/tmp");
+ auto temp_file = NewTempAbsPath();
struct statfs st;
EXPECT_THAT(statfs(temp_file.c_str(), &st), SyscallFailsWithErrno(ENOENT));