diff options
Diffstat (limited to 'test')
45 files changed, 1291 insertions, 373 deletions
diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go index 32bf2a992..d3e5efd4f 100644 --- a/test/iptables/filter_output.go +++ b/test/iptables/filter_output.go @@ -441,9 +441,20 @@ func (FilterOutputDestination) Name() string { // ContainerAction implements TestCase.ContainerAction. func (FilterOutputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, + var rules [][]string + if ipv6 { + rules = [][]string{ + {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, + // Allow solicited node multicast addresses so we can send neighbor + // solicitations. + {"-A", "OUTPUT", "-d", "ff02::1:ff00:0/104", "-j", "ACCEPT"}, + {"-P", "OUTPUT", "DROP"}, + } + } else { + rules = [][]string{ + {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, + {"-P", "OUTPUT", "DROP"}, + } } if err := filterTableRules(ipv6, rules); err != nil { return err diff --git a/test/iptables/nat.go b/test/iptables/nat.go index dd9a18339..b98d99fb8 100644 --- a/test/iptables/nat.go +++ b/test/iptables/nat.go @@ -577,11 +577,18 @@ func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net. connCh := make(chan int) errCh := make(chan error) go func() { - connFD, _, err := syscall.Accept(sockfd) - if err != nil { - errCh <- err + for { + connFD, _, err := syscall.Accept(sockfd) + if errors.Is(err, syscall.EINTR) { + continue + } + if err != nil { + errCh <- err + return + } + connCh <- connFD + return } - connCh <- connFD }() // Wait for accept() to return or for the context to finish. diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 232fdd4d4..9b5994d59 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -57,13 +57,13 @@ def _syscall_test( platform, use_tmpfs, tags, + debug, network = "none", file_access = "exclusive", overlay = False, add_uds_tree = False, vfs2 = False, - fuse = False, - debug = True): + fuse = False): # Prepend "runsc" to non-native platform names. full_platform = platform if platform == "native" else "runsc_" + platform @@ -179,6 +179,7 @@ def syscall_test( use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + vfs2_tags, + debug = debug, vfs2 = True, fuse = fuse, ) @@ -194,6 +195,7 @@ def syscall_test( use_tmpfs = False, add_uds_tree = add_uds_tree, tags = list(tags), + debug = debug, ) for (platform, platform_tags) in platforms.items(): @@ -205,6 +207,7 @@ def syscall_test( use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platform_tags + tags, + debug = debug, ) if add_overlay: @@ -216,6 +219,7 @@ def syscall_test( use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + tags, + debug = debug, overlay = True, ) @@ -232,6 +236,7 @@ def syscall_test( use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + overlay_vfs2_tags, + debug = debug, overlay = True, vfs2 = True, ) @@ -246,6 +251,7 @@ def syscall_test( network = "host", add_uds_tree = add_uds_tree, tags = platforms[default_platform] + tags, + debug = debug, ) if not use_tmpfs: @@ -258,6 +264,7 @@ def syscall_test( use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + tags, + debug = debug, file_access = "shared", ) _syscall_test( @@ -268,6 +275,7 @@ def syscall_test( use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + vfs2_tags, + debug = debug, file_access = "shared", vfs2 = True, ) diff --git a/test/runner/runner.go b/test/runner/runner.go index 22d535f8d..7ab2c3edf 100644 --- a/test/runner/runner.go +++ b/test/runner/runner.go @@ -53,6 +53,9 @@ var ( runscPath = flag.String("runsc", "", "path to runsc binary") addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests") + // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and + // set to true as the default for the test runner. + leakCheck = flag.Bool("leak-check", false, "check for reference leaks") ) // runTestCaseNative runs the test case directly on the host machine. @@ -174,6 +177,9 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { if *addUDSTree { args = append(args, "-fsgofer-host-uds") } + if *leakCheck { + args = append(args, "-ref-leak-mode=log-names") + } testLogDir := "" if undeclaredOutputsDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok { diff --git a/test/runtimes/exclude/java11.csv b/test/runtimes/exclude/java11.csv index d978baca7..e41441374 100644 --- a/test/runtimes/exclude/java11.csv +++ b/test/runtimes/exclude/java11.csv @@ -144,6 +144,7 @@ jdk/jfr/cmd/TestSplit.java,,java.lang.RuntimeException: 'Missing file' missing f jdk/jfr/cmd/TestSummary.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr jdk/jfr/event/compiler/TestCompilerStats.java,,java.lang.RuntimeException: Field nmetodsSize not in event jdk/jfr/event/metadata/TestDefaultConfigurations.java,,Setting 'threshold' in event 'jdk.SecurityPropertyModification' was not configured in the configuration 'default' +jdk/jfr/event/oldobject/TestLargeRootSet.java,,Flaky - `main' threw exception: java.lang.RuntimeException: Could not find root object jdk/jfr/event/runtime/TestActiveSettingEvent.java,,java.lang.Exception: Could not find setting with name jdk.X509Validation#threshold jdk/jfr/event/runtime/TestModuleEvents.java,,java.lang.RuntimeException: assertEquals: expected jdk.proxy1 to equal java.base jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java,, diff --git a/test/runtimes/exclude/nodejs12.4.0.csv b/test/runtimes/exclude/nodejs12.4.0.csv index ba993814f..c4e7917ec 100644 --- a/test/runtimes/exclude/nodejs12.4.0.csv +++ b/test/runtimes/exclude/nodejs12.4.0.csv @@ -1,31 +1,22 @@ test name,bug id,comment async-hooks/test-statwatcher.js,https://github.com/nodejs/node/issues/21425,Check for fix inclusion in nodejs releases after 2020-03-29 -benchmark/test-benchmark-fs.js,, -benchmark/test-benchmark-napi.js,, +benchmark/test-benchmark-fs.js,,Broken test +benchmark/test-benchmark-napi.js,,Broken test doctool/test-make-doc.js,b/68848110,Expected to fail. internet/test-dgram-multicast-set-interface-lo.js,b/162798882, -internet/test-doctool-versions.js,, -internet/test-uv-threadpool-schedule.js,, -parallel/test-cluster-dgram-reuse.js,b/64024294, +internet/test-doctool-versions.js,,Broken test +internet/test-uv-threadpool-schedule.js,,Broken test parallel/test-dgram-bind-fd.js,b/132447356, parallel/test-dgram-socket-buffer-size.js,b/68847921, parallel/test-dns-channel-timeout.js,b/161893056, -parallel/test-fs-access.js,, -parallel/test-fs-watchfile.js,,Flaky - File already exists error -parallel/test-fs-write-stream.js,b/166819807,Flaky -parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky -parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky -parallel/test-http-writable-true-after-close.js,,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2 +parallel/test-fs-access.js,,Broken test +parallel/test-fs-watchfile.js,b/166819807,Flaky - VFS1 only +parallel/test-fs-write-stream.js,b/166819807,Flaky - VFS1 only +parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky - VFS1 only +parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky - VFS1 only +parallel/test-http-writable-true-after-close.js,b/171301436,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2 parallel/test-os.js,b/63997097, -parallel/test-net-server-listen-options.js,,Flaky - EADDRINUSE -parallel/test-process-uid-gid.js,, -parallel/test-tls-cli-min-version-1.0.js,,Flaky - EADDRINUSE -parallel/test-tls-cli-min-version-1.1.js,,Flaky - EADDRINUSE -parallel/test-tls-cli-min-version-1.2.js,,Flaky - EADDRINUSE -parallel/test-tls-cli-min-version-1.3.js,,Flaky - EADDRINUSE -parallel/test-tls-cli-max-version-1.2.js,,Flaky - EADDRINUSE -parallel/test-tls-cli-max-version-1.3.js,,Flaky - EADDRINUSE -parallel/test-tls-min-max-version.js,,Flaky - EADDRINUSE +parallel/test-process-uid-gid.js,,Does not work inside Docker with gid nobody pseudo-tty/test-assert-colors.js,b/162801321, pseudo-tty/test-assert-no-color.js,b/162801321, pseudo-tty/test-assert-position-indicator.js,b/162801321, @@ -48,11 +39,7 @@ pseudo-tty/test-tty-stdout-resize.js,b/162801321, pseudo-tty/test-tty-stream-constructors.js,b/162801321, pseudo-tty/test-tty-window-size.js,b/162801321, pseudo-tty/test-tty-wrap.js,b/162801321, -pummel/test-heapdump-http2.js,,Flaky -pummel/test-net-pingpong.js,, +pummel/test-net-pingpong.js,,Broken test pummel/test-vm-memleak.js,b/162799436, -pummel/test-watch-file.js,,Flaky - Timeout -sequential/test-child-process-pass-fd.js,b/63926391,Flaky -sequential/test-https-connect-localport.js,,Flaky - EADDRINUSE -sequential/test-net-bytes-per-incoming-chunk-overhead.js,,flaky - timeout -tick-processor/test-tick-processor-builtin.js,, +pummel/test-watch-file.js,,Flaky - VFS1 only +tick-processor/test-tick-processor-builtin.js,,Broken test diff --git a/test/runtimes/exclude/php7.3.6.csv b/test/runtimes/exclude/php7.3.6.csv index a73f3bcfb..9e1f4c050 100644 --- a/test/runtimes/exclude/php7.3.6.csv +++ b/test/runtimes/exclude/php7.3.6.csv @@ -26,12 +26,13 @@ ext/standard/tests/file/php_fd_wrapper_01.phpt,, ext/standard/tests/file/php_fd_wrapper_02.phpt,, ext/standard/tests/file/php_fd_wrapper_03.phpt,, ext/standard/tests/file/php_fd_wrapper_04.phpt,, -ext/standard/tests/file/realpath_bug77484.phpt,b/162894969, +ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,VFS1 only failure ext/standard/tests/file/rename_variation.phpt,b/68717309, ext/standard/tests/file/symlink_link_linkinfo_is_link_variation4.phpt,b/162895341, ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223, ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt,, ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt,, +ext/standard/tests/network/bug20134.phpt,b/171347929,Flaky ext/standard/tests/streams/proc_open_bug60120.phpt,,Flaky until php-src 3852a35fdbcb ext/standard/tests/streams/proc_open_bug69900.phpt,,Flaky ext/standard/tests/streams/stream_socket_sendto.phpt,, diff --git a/test/runtimes/exclude/python3.7.3.csv b/test/runtimes/exclude/python3.7.3.csv index 8760f8951..911f22855 100644 --- a/test/runtimes/exclude/python3.7.3.csv +++ b/test/runtimes/exclude/python3.7.3.csv @@ -18,4 +18,3 @@ test_selectors,b/76116849,OSError not raised with epoll test_smtplib,b/162980434,unclosed sockets test_signal,,Flaky - signal: alarm clock test_socket,b/75983380, -test_subprocess,b/162980831, diff --git a/test/runtimes/proctor/main.go b/test/runtimes/proctor/main.go index e5607ac92..81cb68381 100644 --- a/test/runtimes/proctor/main.go +++ b/test/runtimes/proctor/main.go @@ -22,6 +22,7 @@ import ( "log" "os" "strings" + "syscall" "gvisor.dev/gvisor/test/runtimes/proctor/lib" ) @@ -33,6 +34,29 @@ var ( pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children") ) +// setNumFilesLimit changes the NOFILE soft rlimit if it is too high. +func setNumFilesLimit() error { + // In docker containers, the default value of the NOFILE limit is + // 1048576. A few runtime tests (e.g. python:test_subprocess) + // enumerates all possible file descriptors and these tests can fail by + // timeout if the NOFILE limit is too high. On gVisor, syscalls are + // slower so these tests will need even more time to pass. + const nofile = 32768 + rLimit := syscall.Rlimit{} + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return fmt.Errorf("failed to get RLIMIT_NOFILE: %v", err) + } + if rLimit.Cur > nofile { + rLimit.Cur = nofile + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return fmt.Errorf("failed to set RLIMIT_NOFILE: %v", err) + } + } + return nil +} + func main() { flag.Parse() @@ -74,6 +98,10 @@ func main() { tests = strings.Split(*testNames, ",") } + if err := setNumFilesLimit(); err != nil { + log.Fatalf("%v", err) + } + // Run tests. cmds := tr.TestCmds(tests) for _, cmd := range cmds { diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 36b7f1b97..c94c1d5bd 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1285,6 +1285,7 @@ cc_binary( "//test/util:mount_util", "//test/util:multiprocess_util", "//test/util:posix_error", + "//test/util:save_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", @@ -2434,6 +2435,7 @@ cc_library( "@com_google_absl//absl/memory", gtest, "//test/util:posix_error", + "//test/util:save_util", "//test/util:test_util", ], alwayslink = 1, @@ -3441,6 +3443,7 @@ cc_binary( "@com_google_absl//absl/strings", gtest, "//test/util:posix_error", + "//test/util:save_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", @@ -3624,6 +3627,7 @@ cc_binary( "//test/util:signal_util", "//test/util:test_util", "//test/util:thread_util", + "//test/util:timer_util", ], ) diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc index 549141cbb..b286e84fe 100644 --- a/test/syscalls/linux/flock.cc +++ b/test/syscalls/linux/flock.cc @@ -216,14 +216,29 @@ TEST_F(FlockTest, TestSharedLockFailExclusiveHolderBlocking_NoRandomSave) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - // Register a signal handler for SIGALRM and set an alarm that will go off - // while blocking in the subsequent flock() call. This will interrupt flock() - // and cause it to return EINTR. + // Make sure that a blocking flock() call will return EINTR when interrupted + // by a signal. Create a timer that will go off while blocking on flock(), and + // register the corresponding signal handler. + auto timer = ASSERT_NO_ERRNO_AND_VALUE( + TimerCreate(CLOCK_MONOTONIC, sigevent_t{ + .sigev_signo = SIGALRM, + .sigev_notify = SIGEV_SIGNAL, + })); + struct sigaction act = {}; act.sa_handler = trivial_handler; ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); - ASSERT_THAT(ualarm(10000, 0), SyscallSucceeds()); + + // Now that the signal handler is registered, set the timer. Set an interval + // so that it's ok if the timer goes off before we call flock. + ASSERT_NO_ERRNO( + timer.Set(0, itimerspec{ + .it_interval = absl::ToTimespec(absl::Milliseconds(10)), + .it_value = absl::ToTimespec(absl::Milliseconds(10)), + })); + ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallFailsWithErrno(EINTR)); + timer.reset(); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); @@ -258,14 +273,29 @@ TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolderBlocking_NoRandomSave) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - // Register a signal handler for SIGALRM and set an alarm that will go off - // while blocking in the subsequent flock() call. This will interrupt flock() - // and cause it to return EINTR. + // Make sure that a blocking flock() call will return EINTR when interrupted + // by a signal. Create a timer that will go off while blocking on flock(), and + // register the corresponding signal handler. + auto timer = ASSERT_NO_ERRNO_AND_VALUE( + TimerCreate(CLOCK_MONOTONIC, sigevent_t{ + .sigev_signo = SIGALRM, + .sigev_notify = SIGEV_SIGNAL, + })); + struct sigaction act = {}; act.sa_handler = trivial_handler; ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); - ASSERT_THAT(ualarm(10000, 0), SyscallSucceeds()); + + // Now that the signal handler is registered, set the timer. Set an interval + // so that it's ok if the timer goes off before we call flock. + ASSERT_NO_ERRNO( + timer.Set(0, itimerspec{ + .it_interval = absl::ToTimespec(absl::Milliseconds(10)), + .it_value = absl::ToTimespec(absl::Milliseconds(10)), + })); + ASSERT_THAT(flock(fd.get(), LOCK_EX), SyscallFailsWithErrno(EINTR)); + timer.reset(); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc index ae65d366b..1635c6d0c 100644 --- a/test/syscalls/linux/mknod.cc +++ b/test/syscalls/linux/mknod.cc @@ -93,15 +93,15 @@ TEST(MknodTest, MknodOnExistingPathFails) { } TEST(MknodTest, UnimplementedTypesReturnError) { - const std::string path = NewTempAbsPath(); + // TODO(gvisor.dev/issue/1624): These file types are supported by some + // filesystems in VFS2, so this test should be deleted along with VFS1. + SKIP_IF(!IsRunningWithVFS1()); - if (IsRunningWithVFS1()) { - ASSERT_THAT(mknod(path.c_str(), S_IFSOCK, 0), - SyscallFailsWithErrno(EOPNOTSUPP)); - } - // These will fail on linux as well since we don't have CAP_MKNOD. - ASSERT_THAT(mknod(path.c_str(), S_IFCHR, 0), SyscallFailsWithErrno(EPERM)); - ASSERT_THAT(mknod(path.c_str(), S_IFBLK, 0), SyscallFailsWithErrno(EPERM)); + const std::string path = NewTempAbsPath(); + EXPECT_THAT(mknod(path.c_str(), S_IFSOCK, 0), + SyscallFailsWithErrno(EOPNOTSUPP)); + EXPECT_THAT(mknod(path.c_str(), S_IFCHR, 0), SyscallFailsWithErrno(EPERM)); + EXPECT_THAT(mknod(path.c_str(), S_IFBLK, 0), SyscallFailsWithErrno(EPERM)); } TEST(MknodTest, Socket) { @@ -125,6 +125,16 @@ TEST(MknodTest, Socket) { ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds()); } +PosixErrorOr<FileDescriptor> OpenRetryEINTR(std::string const& path, int flags, + mode_t mode = 0) { + while (true) { + auto maybe_fd = Open(path, flags, mode); + if (maybe_fd.ok() || maybe_fd.error().errno_value() != EINTR) { + return maybe_fd; + } + } +} + TEST(MknodTest, Fifo) { const std::string fifo = NewTempAbsPath(); ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0), @@ -139,14 +149,16 @@ TEST(MknodTest, Fifo) { // Read-end of the pipe. ScopedThread t([&fifo, &buf, &msg]() { - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY)); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY)); EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(msg.length())); EXPECT_EQ(msg, std::string(buf.data())); }); // Write-end of the pipe. - FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY)); + FileDescriptor wfd = + ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_WRONLY)); EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()), SyscallSucceedsWithValue(msg.length())); } @@ -164,15 +176,16 @@ TEST(MknodTest, FifoOtrunc) { std::vector<char> buf(512); // Read-end of the pipe. ScopedThread t([&fifo, &buf, &msg]() { - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY)); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY)); EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(msg.length())); EXPECT_EQ(msg, std::string(buf.data())); }); // Write-end of the pipe. - FileDescriptor wfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC)); + FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE( + OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC)); EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()), SyscallSucceedsWithValue(msg.length())); } @@ -192,14 +205,15 @@ TEST(MknodTest, FifoTruncNoOp) { std::vector<char> buf(512); // Read-end of the pipe. ScopedThread t([&fifo, &buf, &msg]() { - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY)); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY)); EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(msg.length())); EXPECT_EQ(msg, std::string(buf.data())); }); - FileDescriptor wfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC)); + FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE( + OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC)); EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()), SyscallSucceedsWithValue(msg.length())); diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index 3aab25b23..d65b7d031 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -34,6 +34,7 @@ #include "test/util/mount_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" +#include "test/util/save_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -131,7 +132,9 @@ TEST(MountTest, UmountDetach) { ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700", /* umountflags= */ MNT_DETACH)); const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_NE(before.st_ino, after.st_ino); + EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino) + << "mount point has device number " << before.st_dev + << " and inode number " << before.st_ino << " before and after mount"; // Create files in the new mount. constexpr char kContents[] = "no no no"; @@ -147,12 +150,14 @@ TEST(MountTest, UmountDetach) { // Unmount the tmpfs. mount.Release()(); - // Only check for inode number equality if the directory is not in overlayfs. - // If xino option is not enabled and if all overlayfs layers do not belong to - // the same filesystem then "the value of st_ino for directory objects may not - // be persistent and could change even while the overlay filesystem is - // mounted." -- Documentation/filesystems/overlayfs.txt - if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) { + // Inode numbers for gofer-accessed files may change across save/restore. + // + // For overlayfs, if xino option is not enabled and if all overlayfs layers do + // not belong to the same filesystem then "the value of st_ino for directory + // objects may not be persistent and could change even while the overlay + // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt + if (!IsRunningWithSaveRestore() && + !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) { const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); EXPECT_EQ(before.st_ino, after2.st_ino); } @@ -214,18 +219,23 @@ TEST(MountTest, MountTmpfs) { const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); EXPECT_EQ(s.st_mode, S_IFDIR | 0700); - EXPECT_NE(s.st_ino, before.st_ino); + EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino) + << "mount point has device number " << before.st_dev + << " and inode number " << before.st_ino << " before and after mount"; EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); } // Now that dir is unmounted again, we should have the old inode back. - // Only check for inode number equality if the directory is not in overlayfs. - // If xino option is not enabled and if all overlayfs layers do not belong to - // the same filesystem then "the value of st_ino for directory objects may not - // be persistent and could change even while the overlay filesystem is - // mounted." -- Documentation/filesystems/overlayfs.txt - if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) { + // + // Inode numbers for gofer-accessed files may change across save/restore. + // + // For overlayfs, if xino option is not enabled and if all overlayfs layers do + // not belong to the same filesystem then "the value of st_ino for directory + // objects may not be persistent and could change even while the overlay + // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt + if (!IsRunningWithSaveRestore() && + !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) { const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); EXPECT_EQ(before.st_ino, after.st_ino); } diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc index b558e3a01..a7c46adbf 100644 --- a/test/syscalls/linux/packet_socket_raw.cc +++ b/test/syscalls/linux/packet_socket_raw.cc @@ -664,6 +664,17 @@ TEST_P(RawPacketTest, SetAndGetSocketLinger) { EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); } +TEST_P(RawPacketTest, GetSocketAcceptConn) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); +} INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest, ::testing::Values(ETH_P_IP, ETH_P_ALL)); diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc index c097c9187..06d9dbf65 100644 --- a/test/syscalls/linux/pipe.cc +++ b/test/syscalls/linux/pipe.cc @@ -569,30 +569,38 @@ TEST_P(PipeTest, Streaming) { // Size() requires 2 syscalls, call it once and remember the value. const int pipe_size = Size(); + const size_t streamed_bytes = 4 * pipe_size; absl::Notification notify; - ScopedThread t([this, ¬ify, pipe_size]() { + ScopedThread t([&, this]() { + std::vector<char> buf(1024); // Don't start until it's full. notify.WaitForNotification(); - for (int i = 0; i < pipe_size; i++) { - int rbuf; - ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf, i); + ssize_t total = 0; + while (total < streamed_bytes) { + ASSERT_THAT(read(rfd_.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + total += buf.size(); } }); // Write 4 bytes * pipe_size. It will fill up the pipe once, notify the reader // to start. Then we write pipe size worth 3 more times to ensure the reader // can follow along. + // + // The size of each write (which is determined by buf.size()) must be smaller + // than the size of the pipe (which, in the "smallbuffer" configuration, is 1 + // page) for the check for notify.Notify() below to be correct. + std::vector<char> buf(1024); + RandomizeBuffer(buf.data(), buf.size()); ssize_t total = 0; - for (int i = 0; i < pipe_size; i++) { - ssize_t written = write(wfd_.get(), &i, sizeof(i)); - ASSERT_THAT(written, SyscallSucceedsWithValue(sizeof(i))); - total += written; + while (total < streamed_bytes) { + ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + total += buf.size(); // Is the next write about to fill up the buffer? Wake up the reader once. - if (total < pipe_size && (total + written) >= pipe_size) { + if (total < pipe_size && (total + buf.size()) >= pipe_size) { notify.Notify(); } } diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index e8fcc4439..7a0f33dff 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -26,6 +26,7 @@ #include <string.h> #include <sys/mman.h> #include <sys/prctl.h> +#include <sys/ptrace.h> #include <sys/stat.h> #include <sys/statfs.h> #include <sys/utsname.h> @@ -512,6 +513,414 @@ TEST(ProcSelfAuxv, EntryValues) { EXPECT_EQ(i, proc_auxv.size()); } +// Just open and read a part of /proc/self/mem, check that we can read an item. +TEST(ProcPidMem, Read) { + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + char input[] = "hello-world"; + char output[sizeof(input)]; + ASSERT_THAT(pread(memfd.get(), output, sizeof(output), + reinterpret_cast<off_t>(input)), + SyscallSucceedsWithValue(sizeof(input))); + ASSERT_STREQ(input, output); +} + +// Perform read on an unmapped region. +TEST(ProcPidMem, Unmapped) { + // Strategy: map then unmap, so we have a guaranteed unmapped region + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); + // Fill it with things + memset(mapping.ptr(), 'x', mapping.len()); + char expected = 'x', output; + ASSERT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(mapping.ptr())), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(expected, output); + + // Unmap region again + ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds()); + + // Now we want EIO error + ASSERT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(mapping.ptr())), + SyscallFailsWithErrno(EIO)); +} + +// Perform read repeatedly to verify offset change. +TEST(ProcPidMem, RepeatedRead) { + auto const num_reads = 3; + char expected[] = "01234567890abcdefghijkl"; + char output[sizeof(expected) / num_reads]; + + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); + for (auto i = 0; i < num_reads; i++) { + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[i * sizeof(output)], output, sizeof(output)), + 0); + } +} + +// Perform seek operations repeatedly. +TEST(ProcPidMem, RepeatedSeek) { + auto const num_reads = 3; + char expected[] = "01234567890abcdefghijkl"; + char output[sizeof(expected) / num_reads]; + + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); + // Read from start + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0); + // Skip ahead one read + ASSERT_THAT(lseek(memfd.get(), sizeof(output), SEEK_CUR), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected) + + sizeof(output) * 2)); + // Do read again + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[2 * sizeof(output)], output, sizeof(output)), 0); + // Skip back three reads + ASSERT_THAT(lseek(memfd.get(), -3 * sizeof(output), SEEK_CUR), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); + // Do read again + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0); + // Check that SEEK_END does not work + ASSERT_THAT(lseek(memfd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL)); +} + +// Perform read past an allocated memory region. +TEST(ProcPidMem, PartialRead) { + // Strategy: map large region, then do unmap and remap smaller region + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + + Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); + ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds()); + Mapping smaller_mapping = ASSERT_NO_ERRNO_AND_VALUE( + Mmap(mapping.ptr(), kPageSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + + // Fill it with things + memset(smaller_mapping.ptr(), 'x', smaller_mapping.len()); + + // Now we want no error + char expected[] = {'x'}; + std::unique_ptr<char[]> output(new char[kPageSize]); + off_t read_offset = + reinterpret_cast<off_t>(smaller_mapping.ptr()) + kPageSize - 1; + ASSERT_THAT( + pread(memfd.get(), output.get(), sizeof(output.get()), read_offset), + SyscallSucceedsWithValue(sizeof(expected))); + // Since output is larger, than expected we have to do manual compare + ASSERT_EQ(expected[0], (output).get()[0]); +} + +// Perform read on /proc/[pid]/mem after exit. +TEST(ProcPidMem, AfterExit) { + int pfd1[2] = {}; + int pfd2[2] = {}; + + char expected[] = "hello-world"; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char ok = 1; + TEST_CHECK(WriteFd(pfd1[1], &ok, sizeof(ok)) == sizeof(ok)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Wait for child to be alive and well + char ok = 0; + EXPECT_THAT(ReadFd(pfd1[0], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + // Open /proc/pid/mem fd + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); + + // Expect that we can read + char output[sizeof(expected)]; + EXPECT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(&expected)), + SyscallSucceedsWithValue(sizeof(output))); + EXPECT_STREQ(expected, output); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); + + // Expect that we can't read anymore + EXPECT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(&expected)), + SyscallSucceedsWithValue(0)); +} + +// Read from /proc/[pid]/mem with different UID/GID and attached state. +TEST(ProcPidMem, DifferentUserAttached) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_DAC_OVERRIDE))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_PTRACE))); + + int pfd1[2] = {}; + int pfd2[2] = {}; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char input[] = "hello-world"; + off_t input_location = reinterpret_cast<off_t>(input); + TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == + sizeof(input_location)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + char ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Read target location from child + off_t target_location; + EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), + SyscallSucceedsWithValue(sizeof(target_location))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + ScopedThread([&] { + // Attach to child subprocess without stopping it + EXPECT_THAT(ptrace(PTRACE_SEIZE, child_pid, NULL, NULL), SyscallSucceeds()); + + // Keep capabilities after setuid + EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); + constexpr int kNobody = 65534; + EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); + + // Only restore CAP_SYS_PTRACE and CAP_DAC_OVERRIDE + EXPECT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, true)); + EXPECT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, true)); + + // Open /proc/pid/mem fd + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); + char expected[] = "hello-world"; + char output[sizeof(expected)]; + EXPECT_THAT(pread(memfd.get(), output, sizeof(output), + reinterpret_cast<off_t>(target_location)), + SyscallSucceedsWithValue(sizeof(output))); + EXPECT_STREQ(expected, output); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + char ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << " status " << status; + }); +} + +// Attempt to read from /proc/[pid]/mem with different UID/GID. +TEST(ProcPidMem, DifferentUser) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); + + int pfd1[2] = {}; + int pfd2[2] = {}; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char input[] = "hello-world"; + off_t input_location = reinterpret_cast<off_t>(input); + TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == + sizeof(input_location)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + char ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Read target location from child + off_t target_location; + EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), + SyscallSucceedsWithValue(sizeof(target_location))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + ScopedThread([&] { + constexpr int kNobody = 65534; + EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); + + // Attempt to open /proc/[child_pid]/mem + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + EXPECT_THAT(open(mempath.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + char ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); + }); +} + +// Perform read on /proc/[pid]/mem with same UID/GID. +TEST(ProcPidMem, SameUser) { + int pfd1[2] = {}; + int pfd2[2] = {}; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char input[] = "hello-world"; + off_t input_location = reinterpret_cast<off_t>(input); + TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == + sizeof(input_location)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + char ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Read target location from child + off_t target_location; + EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), + SyscallSucceedsWithValue(sizeof(target_location))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + // Open /proc/pid/mem fd + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); + char expected[] = "hello-world"; + char output[sizeof(expected)]; + EXPECT_THAT(pread(memfd.get(), output, sizeof(output), + reinterpret_cast<off_t>(target_location)), + SyscallSucceedsWithValue(sizeof(output))); + EXPECT_STREQ(expected, output); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + char ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); +} + // Just open and read /proc/self/maps, check that we can find [stack] TEST(ProcSelfMaps, Basic) { auto proc_self_maps = diff --git a/test/syscalls/linux/proc_pid_smaps.cc b/test/syscalls/linux/proc_pid_smaps.cc index 9fb1b3a2c..738923822 100644 --- a/test/syscalls/linux/proc_pid_smaps.cc +++ b/test/syscalls/linux/proc_pid_smaps.cc @@ -191,7 +191,7 @@ PosixErrorOr<std::vector<ProcPidSmapsEntry>> ParseProcPidSmaps( // amount of whitespace). if (!entry) { std::cerr << "smaps line not considered a maps line: " - << maybe_maps_entry.error_message() << std::endl; + << maybe_maps_entry.error().message() << std::endl; return PosixError( EINVAL, absl::StrCat("smaps field line without preceding maps line: ", l)); diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc index 1b9dbc584..bd779da92 100644 --- a/test/syscalls/linux/raw_socket_icmp.cc +++ b/test/syscalls/linux/raw_socket_icmp.cc @@ -438,6 +438,19 @@ TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); } +// Test getsockopt for SO_ACCEPTCONN. +TEST_F(RawSocketICMPTest, GetSocketAcceptConn) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); +} + void RawSocketICMPTest::ExpectICMPSuccess(const struct icmphdr& icmp) { // We're going to receive both the echo request and reply, but the order is // indeterminate. diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc index e9b131ca9..ed6a1c2aa 100644 --- a/test/syscalls/linux/semaphore.cc +++ b/test/syscalls/linux/semaphore.cc @@ -486,6 +486,62 @@ TEST(SemaphoreTest, SemIpcSet) { ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES)); } +TEST(SemaphoreTest, SemCtlIpcStat) { + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + const uid_t kUid = getuid(); + const gid_t kGid = getgid(); + time_t start_time = time(nullptr); + + AutoSem sem(semget(IPC_PRIVATE, 10, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + + struct semid_ds ds; + EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds()); + + EXPECT_EQ(ds.sem_perm.__key, IPC_PRIVATE); + EXPECT_EQ(ds.sem_perm.uid, kUid); + EXPECT_EQ(ds.sem_perm.gid, kGid); + EXPECT_EQ(ds.sem_perm.cuid, kUid); + EXPECT_EQ(ds.sem_perm.cgid, kGid); + EXPECT_EQ(ds.sem_perm.mode, 0600); + // Last semop time is not set on creation. + EXPECT_EQ(ds.sem_otime, 0); + EXPECT_GE(ds.sem_ctime, start_time); + EXPECT_EQ(ds.sem_nsems, 10); + + // The timestamps only have a resolution of seconds; slow down so we actually + // see the timestamps change. + absl::SleepFor(absl::Seconds(1)); + + // Set semid_ds structure of the set. + auto last_ctime = ds.sem_ctime; + start_time = time(nullptr); + struct semid_ds semid_to_set = {}; + semid_to_set.sem_perm.uid = kUid; + semid_to_set.sem_perm.gid = kGid; + semid_to_set.sem_perm.mode = 0666; + ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds()); + struct sembuf buf = {}; + buf.sem_op = 1; + ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); + + EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.mode, 0666); + EXPECT_GE(ds.sem_otime, start_time); + EXPECT_GT(ds.sem_ctime, last_ctime); + + // An invalid semid fails the syscall with errno EINVAL. + EXPECT_THAT(semctl(sem.get() + 1, 0, IPC_STAT, &ds), + SyscallFailsWithErrno(EINVAL)); + + // Make semaphore not readable and check the signal fails. + semid_to_set.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds()); + EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), + SyscallFailsWithErrno(EACCES)); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index 11fcec443..39a68c5a5 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -350,6 +350,10 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) { sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); + // TODO(b/157236388): Remove Disable save after bug is fixed. S/R test can + // fail because the last socket may not be delivered to the accept queue + // by the time connect returns. + DisableSave ds; for (int i = 0; i < kBacklog; i++) { auto client = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); @@ -554,7 +558,11 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownWhileConnect) { }); } -TEST_P(SocketInetLoopbackTest, TCPbacklog) { +// TODO(b/157236388): Remove _NoRandomSave once bug is fixed. Test fails w/ +// random save as established connections which can't be delivered to the accept +// queue because the queue is full are not correctly delivered after restore +// causing the last accept to timeout on the restore. +TEST_P(SocketInetLoopbackTest, TCPbacklog_NoRandomSave) { auto const& param = GetParam(); TestAddress const& listener = param.listener; @@ -567,7 +575,8 @@ TEST_P(SocketInetLoopbackTest, TCPbacklog) { ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len), SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), 2), SyscallSucceeds()); + constexpr int kBacklogSize = 2; + ASSERT_THAT(listen(listen_fd.get(), kBacklogSize), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; @@ -1143,6 +1152,9 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); + + // TODO(b/157236388): Reenable Cooperative S/R once bug is fixed. + DisableSave ds; ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_addr), connector.addr_len), diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc index f4b69c46c..831d96262 100644 --- a/test/syscalls/linux/socket_ip_tcp_generic.cc +++ b/test/syscalls/linux/socket_ip_tcp_generic.cc @@ -14,6 +14,7 @@ #include "test/syscalls/linux/socket_ip_tcp_generic.h" +#include <fcntl.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <poll.h> @@ -979,6 +980,56 @@ TEST_P(TCPSocketPairTest, SetTCPUserTimeoutAboveZero) { EXPECT_EQ(get, kAbove); } +#ifdef __linux__ +TEST_P(TCPSocketPairTest, SpliceFromPipe) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + // Fill with some random data. + std::vector<char> buf(kPageSize / 2); + RandomizeBuffer(buf.data(), buf.size()); + ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + + EXPECT_THAT( + splice(rfd.get(), nullptr, sockets->first_fd(), nullptr, kPageSize, 0), + SyscallSucceedsWithValue(buf.size())); + + std::vector<char> rbuf(buf.size()); + ASSERT_THAT(read(sockets->second_fd(), rbuf.data(), rbuf.size()), + SyscallSucceedsWithValue(buf.size())); + EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); +} + +TEST_P(TCPSocketPairTest, SpliceToPipe) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + // Fill with some random data. + std::vector<char> buf(kPageSize / 2); + RandomizeBuffer(buf.data(), buf.size()); + ASSERT_THAT(write(sockets->first_fd(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + shutdown(sockets->first_fd(), SHUT_WR); + EXPECT_THAT( + splice(sockets->second_fd(), nullptr, wfd.get(), nullptr, kPageSize, 0), + SyscallSucceedsWithValue(buf.size())); + + std::vector<char> rbuf(buf.size()); + ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), + SyscallSucceedsWithValue(buf.size())); + EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); +} +#endif // __linux__ + TEST_P(TCPSocketPairTest, SetTCPWindowClampBelowMinRcvBufConnectedSocket) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); // Discover minimum receive buf by setting a really low value diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc index 3f2c0fdf2..f69f8f99f 100644 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ b/test/syscalls/linux/socket_ip_udp_generic.cc @@ -472,5 +472,19 @@ TEST_P(UDPSocketPairTest, SetAndGetSocketLinger) { EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); } +// Test getsockopt for SO_ACCEPTCONN on udp socket. +TEST_P(UDPSocketPairTest, GetSocketAcceptConn) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT( + getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index a72c76c97..b3f54e7f6 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -28,6 +28,7 @@ #include "test/syscalls/linux/ip_socket_test_util.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/util/posix_error.h" +#include "test/util/save_util.h" #include "test/util/test_util.h" namespace gvisor { @@ -75,7 +76,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNoGroup) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; EXPECT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } @@ -209,7 +210,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackAddr) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -265,7 +266,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -321,7 +322,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddr) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -377,7 +378,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -437,7 +438,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrConnect) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -497,7 +498,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -553,7 +554,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelf) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -609,7 +610,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -669,7 +670,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfConnect) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; EXPECT_THAT( - RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } @@ -727,7 +728,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; EXPECT_THAT( - RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } @@ -785,7 +786,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfNoLoop) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -845,7 +846,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); @@ -919,7 +920,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropAddr) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; EXPECT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } @@ -977,7 +978,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; EXPECT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } @@ -1330,8 +1331,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionOnTwoSockets) { // Check that we received the multicast packet on both sockets. for (auto& sockets : socket_pairs) { char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf, - sizeof(recv_buf), 1 /*timeout*/), + ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf), + 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1409,8 +1410,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) { // Check that we received the multicast packet on both sockets. for (auto& sockets : socket_pairs) { char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf, - sizeof(recv_buf), 1 /*timeout*/), + ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf), + 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1432,8 +1433,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) { char recv_buf[sizeof(send_buf)] = {}; for (auto& sockets : socket_pairs) { - ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf, - sizeof(recv_buf), 1 /*timeout*/), + ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf), + 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } } @@ -1486,7 +1487,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenJoinThenReceive) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1530,7 +1531,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenNoJoinThenNoReceive) { // Check that we don't receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), PosixErrorIs(EAGAIN, ::testing::_)); } @@ -1580,7 +1581,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenSend) { // Check that we received the packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1627,7 +1628,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenReceive) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1678,7 +1679,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenSend) { // Check that we received the packet. char recv_buf[sizeof(send_buf)] = {}; ASSERT_THAT( - RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1737,7 +1738,7 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) { // of the other sockets to have received it, but we will check that later. char recv_buf[sizeof(send_buf)] = {}; EXPECT_THAT( - RecvMsgTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + RecvTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(send_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1745,9 +1746,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) { // Verify that no other messages were received. for (auto& socket : sockets) { char recv_buf[kMessageSize] = {}; - EXPECT_THAT(RecvMsgTimeout(socket->get(), recv_buf, sizeof(recv_buf), - 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); + EXPECT_THAT( + RecvTimeout(socket->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } } @@ -2108,6 +2109,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) { constexpr int kMessageSize = 10; + // Saving during each iteration of the following loop is too expensive. + DisableSave ds; + for (int i = 0; i < 100; ++i) { // Send a new message to the REUSEADDR/REUSEPORT group. We use a new socket // each time so that a new ephemerial port will be used each time. This @@ -2120,16 +2124,18 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) { SyscallSucceedsWithValue(sizeof(send_buf))); } + ds.reset(); + // Check that both receivers got messages. This checks that we are using load // balancing (REUSEPORT) instead of the most recently bound socket // (REUSEADDR). char recv_buf[kMessageSize] = {}; - EXPECT_THAT(RecvMsgTimeout(receiver1->get(), recv_buf, sizeof(recv_buf), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(kMessageSize)); - EXPECT_THAT(RecvMsgTimeout(receiver2->get(), recv_buf, sizeof(recv_buf), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(kMessageSize)); + EXPECT_THAT( + RecvTimeout(receiver1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(kMessageSize)); + EXPECT_THAT( + RecvTimeout(receiver2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(kMessageSize)); } // Test that socket will receive packet info control message. @@ -2193,8 +2199,8 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) { received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); received_msg.msg_control = received_cmsg_buf; - ASSERT_THAT(RetryEINTR(recvmsg)(receiver->get(), &received_msg, 0), - SyscallSucceedsWithValue(kDataLength)); + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); ASSERT_NE(cmsg, nullptr); diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc index 49a0f06d9..875016812 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc @@ -40,17 +40,9 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { /*prefixlen=*/24, &addr, sizeof(addr))); Cleanup defer_addr_removal = Cleanup( [loopback_link = std::move(loopback_link), addr = std::move(addr)] { - if (IsRunningOnGvisor()) { - // TODO(gvisor.dev/issue/3921): Remove this once deleting addresses - // via netlink is supported. - EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr)), - PosixErrorIs(EOPNOTSUPP, ::testing::_)); - } else { - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, - sizeof(addr))); - } + EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, + sizeof(addr))); }); auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -124,17 +116,9 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { 24 /* prefixlen */, &addr, sizeof(addr))); Cleanup defer_addr_removal = Cleanup( [loopback_link = std::move(loopback_link), addr = std::move(addr)] { - if (IsRunningOnGvisor()) { - // TODO(gvisor.dev/issue/3921): Remove this once deleting addresses - // via netlink is supported. - EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr)), - PosixErrorIs(EOPNOTSUPP, ::testing::_)); - } else { - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, - sizeof(addr))); - } + EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, + sizeof(addr))); }); TestAddress broadcast_address("SubnetBroadcastAddress"); diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index b3fcf8e7c..e83f0d81f 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -13,6 +13,7 @@ // limitations under the License. #include <arpa/inet.h> +#include <fcntl.h> #include <ifaddrs.h> #include <linux/if.h> #include <linux/netlink.h> @@ -335,6 +336,49 @@ TEST(NetlinkRouteTest, MsgHdrMsgTrunc) { EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC); } +TEST(NetlinkRouteTest, SpliceFromPipe) { + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); + + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + 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 = loopback_link.index; + + ASSERT_THAT(write(wfd.get(), &req, sizeof(req)), + SyscallSucceedsWithValue(sizeof(req))); + + EXPECT_THAT(splice(rfd.get(), nullptr, fd.get(), nullptr, sizeof(req) + 1, 0), + SyscallSucceedsWithValue(sizeof(req))); + close(wfd.release()); + EXPECT_THAT(splice(rfd.get(), nullptr, fd.get(), nullptr, sizeof(req) + 1, 0), + SyscallSucceedsWithValue(0)); + + bool found = false; + ASSERT_NO_ERRNO(NetlinkResponse( + fd, + [&](const struct nlmsghdr* hdr) { + CheckLinkMsg(hdr, loopback_link); + found = true; + }, + false)); + EXPECT_TRUE(found) << "Netlink response does not contain any links."; +} + TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) { FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); @@ -467,53 +511,42 @@ TEST(NetlinkRouteTest, LookupAll) { ASSERT_GT(count, 0); } -TEST(NetlinkRouteTest, AddAddr) { +TEST(NetlinkRouteTest, AddAndRemoveAddr) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + // Don't do cooperative save/restore because netstack state is not restored. + // TODO(gvisor.dev/issue/4595): enable cooperative save tests. + const DisableSave ds; 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 ifaddrmsg ifa; - struct rtattr rtattr; - struct in_addr addr; - char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_type = RTM_NEWADDR; - req.hdr.nlmsg_seq = kSeq; - req.ifa.ifa_family = AF_INET; - req.ifa.ifa_prefixlen = 24; - req.ifa.ifa_flags = 0; - req.ifa.ifa_scope = 0; - req.ifa.ifa_index = loopback_link.index; - req.rtattr.rta_type = IFA_LOCAL; - req.rtattr.rta_len = RTA_LENGTH(sizeof(req.addr)); - inet_pton(AF_INET, "10.0.0.1", &req.addr); - req.hdr.nlmsg_len = - NLMSG_LENGTH(sizeof(req.ifa)) + NLMSG_ALIGN(req.rtattr.rta_len); + struct in_addr addr; + ASSERT_EQ(inet_pton(AF_INET, "10.0.0.1", &addr), 1); // Create should succeed, as no such address in kernel. - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; - EXPECT_NO_ERRNO( - NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len)); + ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, sizeof(addr))); + + Cleanup defer_addr_removal = Cleanup( + [loopback_link = std::move(loopback_link), addr = std::move(addr)] { + // First delete should succeed, as address exists. + EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, + sizeof(addr))); + + // Second delete should fail, as address no longer exists. + EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, sizeof(addr)), + PosixErrorIs(EINVAL, ::testing::_)); + }); // Replace an existing address should succeed. - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK; - req.hdr.nlmsg_seq++; - EXPECT_NO_ERRNO( - NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len)); + ASSERT_NO_ERRNO(LinkReplaceLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, sizeof(addr))); // Create exclusive should fail, as we created the address above. - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; - req.hdr.nlmsg_seq++; - EXPECT_THAT( - NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len), - PosixErrorIs(EEXIST, ::testing::_)); + EXPECT_THAT(LinkAddExclusiveLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/24, &addr, sizeof(addr)), + PosixErrorIs(EEXIST, ::testing::_)); } // GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request. diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc index 7a0bad4cb..46f749c7c 100644 --- a/test/syscalls/linux/socket_netlink_route_util.cc +++ b/test/syscalls/linux/socket_netlink_route_util.cc @@ -29,6 +29,8 @@ constexpr uint32_t kSeq = 12345; // Types of address modifications that may be performed on an interface. enum class LinkAddrModification { kAdd, + kAddExclusive, + kReplace, kDelete, }; @@ -40,6 +42,14 @@ PosixError PopulateNlmsghdr(LinkAddrModification modification, hdr->nlmsg_type = RTM_NEWADDR; hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; return NoError(); + case LinkAddrModification::kAddExclusive: + hdr->nlmsg_type = RTM_NEWADDR; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK; + return NoError(); + case LinkAddrModification::kReplace: + hdr->nlmsg_type = RTM_NEWADDR; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK; + return NoError(); case LinkAddrModification::kDelete: hdr->nlmsg_type = RTM_DELADDR; hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -144,6 +154,18 @@ PosixError LinkAddLocalAddr(int index, int family, int prefixlen, LinkAddrModification::kAdd); } +PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen, + const void* addr, int addrlen) { + return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, + LinkAddrModification::kAddExclusive); +} + +PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen, + const void* addr, int addrlen) { + return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, + LinkAddrModification::kReplace); +} + PosixError LinkDelLocalAddr(int index, int family, int prefixlen, const void* addr, int addrlen) { return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h index e5badca70..eaa91ad79 100644 --- a/test/syscalls/linux/socket_netlink_route_util.h +++ b/test/syscalls/linux/socket_netlink_route_util.h @@ -39,10 +39,19 @@ PosixErrorOr<std::vector<Link>> DumpLinks(); // Returns the loopback link on the system. ENOENT if not found. PosixErrorOr<Link> LoopbackLink(); -// LinkAddLocalAddr sets IFA_LOCAL attribute on the interface. +// LinkAddLocalAddr adds a new IFA_LOCAL address to the interface. PosixError LinkAddLocalAddr(int index, int family, int prefixlen, const void* addr, int addrlen); +// LinkAddExclusiveLocalAddr adds a new IFA_LOCAL address with NLM_F_EXCL flag +// to the interface. +PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen, + const void* addr, int addrlen); + +// LinkReplaceLocalAddr replaces an IFA_LOCAL address on the interface. +PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen, + const void* addr, int addrlen); + // LinkDelLocalAddr removes IFA_LOCAL attribute on the interface. PosixError LinkDelLocalAddr(int index, int family, int prefixlen, const void* addr, int addrlen); diff --git a/test/syscalls/linux/socket_netlink_util.cc b/test/syscalls/linux/socket_netlink_util.cc index 952eecfe8..bdebea321 100644 --- a/test/syscalls/linux/socket_netlink_util.cc +++ b/test/syscalls/linux/socket_netlink_util.cc @@ -67,10 +67,21 @@ PosixError NetlinkRequestResponse( RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(sendmsg)(fd.get(), &msg, 0)); + return NetlinkResponse(fd, fn, expect_nlmsgerr); +} + +PosixError NetlinkResponse( + const FileDescriptor& fd, + const std::function<void(const struct nlmsghdr* hdr)>& fn, + bool expect_nlmsgerr) { constexpr size_t kBufferSize = 4096; std::vector<char> buf(kBufferSize); + struct iovec iov = {}; iov.iov_base = buf.data(); iov.iov_len = buf.size(); + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; // If NLM_F_MULTI is set, response is a series of messages that ends with a // NLMSG_DONE message. diff --git a/test/syscalls/linux/socket_netlink_util.h b/test/syscalls/linux/socket_netlink_util.h index e13ead406..f97276d44 100644 --- a/test/syscalls/linux/socket_netlink_util.h +++ b/test/syscalls/linux/socket_netlink_util.h @@ -41,6 +41,14 @@ PosixError NetlinkRequestResponse( const std::function<void(const struct nlmsghdr* hdr)>& fn, bool expect_nlmsgerr); +// Call fn on all response netlink messages. +// +// To be used on requests with NLM_F_MULTI reponses. +PosixError NetlinkResponse( + const FileDescriptor& fd, + const std::function<void(const struct nlmsghdr* hdr)>& fn, + bool expect_nlmsgerr); + // Send the passed request and call fn on all response netlink messages. // // To be used on requests without NLM_F_MULTI reponses. diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index e11792309..a760581b5 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -753,8 +753,7 @@ PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size) { return ret; } -PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size, - int timeout) { +PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout) { fd_set rfd; struct timeval to = {.tv_sec = timeout, .tv_usec = 0}; FD_ZERO(&rfd); @@ -767,6 +766,19 @@ PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size, return ret; } +PosixErrorOr<int> RecvMsgTimeout(int sock, struct msghdr* msg, int timeout) { + fd_set rfd; + struct timeval to = {.tv_sec = timeout, .tv_usec = 0}; + FD_ZERO(&rfd); + FD_SET(sock, &rfd); + + int ret; + RETURN_ERROR_IF_SYSCALL_FAIL(ret = select(1, &rfd, NULL, NULL, &to)); + RETURN_ERROR_IF_SYSCALL_FAIL( + ret = RetryEINTR(recvmsg)(sock, msg, MSG_DONTWAIT)); + return ret; +} + void RecvNoData(int sock) { char data = 0; struct iovec iov; diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h index 468bc96e0..5e205339f 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -467,9 +467,12 @@ PosixError FreeAvailablePort(int port); // SendMsg converts a buffer to an iovec and adds it to msg before sending it. PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size); -// RecvMsgTimeout calls select on sock with timeout and then calls recv on sock. -PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size, - int timeout); +// RecvTimeout calls select on sock with timeout and then calls recv on sock. +PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout); + +// RecvMsgTimeout calls select on sock with timeout and then calls recvmsg on +// sock. +PosixErrorOr<int> RecvMsgTimeout(int sock, msghdr* msg, int timeout); // RecvNoData checks that no data is receivable on sock. void RecvNoData(int sock); diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc index 1edcb15a7..ad9c4bf37 100644 --- a/test/syscalls/linux/socket_unix_stream.cc +++ b/test/syscalls/linux/socket_unix_stream.cc @@ -121,6 +121,19 @@ TEST_P(StreamUnixSocketPairTest, SetAndGetSocketLinger) { EXPECT_EQ(0, memcmp(&got_linger, &sl, length)); } +TEST_P(StreamUnixSocketPairTest, GetSocketAcceptConn) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT( + getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); +} + INSTANTIATE_TEST_SUITE_P( AllUnixDomainSockets, StreamUnixSocketPairTest, ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc index 92260b1e1..6e7142a42 100644 --- a/test/syscalls/linux/stat.cc +++ b/test/syscalls/linux/stat.cc @@ -31,6 +31,7 @@ #include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" +#include "test/util/save_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -328,7 +329,10 @@ TEST_F(StatTest, LeadingDoubleSlash) { ASSERT_THAT(lstat(double_slash_path.c_str(), &double_slash_st), SyscallSucceeds()); EXPECT_EQ(st.st_dev, double_slash_st.st_dev); - EXPECT_EQ(st.st_ino, double_slash_st.st_ino); + // Inode numbers for gofer-accessed files may change across save/restore. + if (!IsRunningWithSaveRestore()) { + EXPECT_EQ(st.st_ino, double_slash_st.st_ino); + } } // Test that a rename doesn't change the underlying file. @@ -346,8 +350,14 @@ TEST_F(StatTest, StatDoesntChangeAfterRename) { EXPECT_EQ(st_old.st_nlink, st_new.st_nlink); EXPECT_EQ(st_old.st_dev, st_new.st_dev); + // Inode numbers for gofer-accessed files on which no reference is held may + // change across save/restore because the information that the gofer client + // uses to track file identity (9P QID path) is inconsistent between gofer + // processes, which are restarted across save/restore. + // // Overlay filesystems may synthesize directory inode numbers on the fly. - if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) { + if (!IsRunningWithSaveRestore() && + !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) { EXPECT_EQ(st_old.st_ino, st_new.st_ino); } EXPECT_EQ(st_old.st_mode, st_new.st_mode); @@ -541,6 +551,26 @@ TEST_F(StatTest, LstatELOOPPath) { ASSERT_THAT(lstat(path.c_str(), &s), SyscallFailsWithErrno(ELOOP)); } +TEST(SimpleStatTest, DifferentFilesHaveDifferentDeviceInodeNumberPairs) { + // TODO(gvisor.dev/issue/1624): This test case fails in VFS1 save/restore + // tests because VFS1 gofer inode number assignment restarts after + // save/restore, such that the inodes for file1 and file2 (which are + // unreferenced and therefore not retained in sentry checkpoints before the + // calls to lstat()) are assigned the same inode number. + SKIP_IF(IsRunningWithVFS1() && IsRunningWithSaveRestore()); + + TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + MaybeSave(); + struct stat st1 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file1.path())); + MaybeSave(); + struct stat st2 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file2.path())); + EXPECT_FALSE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) + << "both files have device number " << st1.st_dev << " and inode number " + << st1.st_ino; +} + // Ensure that inode allocation for anonymous devices work correctly across // save/restore. In particular, inode numbers should be unique across S/R. TEST(SimpleStatTest, AnonDeviceAllocatesUniqueInodesAcrossSaveRestore) { diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 9f522f833..ebd873068 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -1725,6 +1725,63 @@ TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) { ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout)); } +// Tests that SO_ACCEPTCONN returns non zero value for listening sockets. +TEST_P(TcpSocketTest, GetSocketAcceptConnListener) { + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT(getsockopt(listener_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceeds()); + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 1); +} + +// Tests that SO_ACCEPTCONN returns zero value for not listening sockets. +TEST_P(TcpSocketTest, GetSocketAcceptConnNonListener) { + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceeds()); + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); + + ASSERT_THAT(getsockopt(t_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceeds()); + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); +} + +TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) { + // TODO(b/171345701): Fix the TCP state for listening socket on shutdown. + SKIP_IF(IsRunningOnGvisor()); + + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + // Initialize address to the loopback one. + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + // Bind to some port then start listening. + ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + ASSERT_THAT(listen(s.get(), SOMAXCONN), SyscallSucceeds()); + + int got = -1; + socklen_t length = sizeof(got); + ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceeds()); + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 1); + + EXPECT_THAT(shutdown(s.get(), SHUT_RD), SyscallSucceeds()); + ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), + SyscallSucceeds()); + ASSERT_EQ(length, sizeof(got)); + EXPECT_EQ(got, 0); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest, ::testing::Values(AF_INET, AF_INET6)); diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc index 4b3c44527..cac94d9e1 100644 --- a/test/syscalls/linux/timers.cc +++ b/test/syscalls/linux/timers.cc @@ -33,6 +33,7 @@ #include "test/util/signal_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" +#include "test/util/timer_util.h" ABSL_FLAG(bool, timers_test_sleep, false, "If true, sleep forever instead of running tests."); @@ -215,99 +216,6 @@ TEST(TimerTest, ProcessKilledOnCPUHardLimit) { EXPECT_GE(cpu, kHardLimit); } -// RAII type for a kernel "POSIX" interval timer. (The kernel provides system -// calls such as timer_create that behave very similarly, but not identically, -// to those described by timer_create(2); in particular, the kernel does not -// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on -// these kernel interval timers.) -// -// Compare implementation to FileDescriptor. -class IntervalTimer { - public: - IntervalTimer() = default; - - explicit IntervalTimer(int id) { set_id(id); } - - IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {} - - IntervalTimer& operator=(IntervalTimer&& orig) { - if (this == &orig) return *this; - reset(orig.release()); - return *this; - } - - IntervalTimer(const IntervalTimer& other) = delete; - IntervalTimer& operator=(const IntervalTimer& other) = delete; - - ~IntervalTimer() { reset(); } - - int get() const { return id_; } - - int release() { - int const id = id_; - id_ = -1; - return id; - } - - void reset() { reset(-1); } - - void reset(int id) { - if (id_ >= 0) { - TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0); - MaybeSave(); - } - set_id(id); - } - - PosixErrorOr<struct itimerspec> Set( - int flags, const struct itimerspec& new_value) const { - struct itimerspec old_value = {}; - if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) { - return PosixError(errno, "timer_settime"); - } - MaybeSave(); - return old_value; - } - - PosixErrorOr<struct itimerspec> Get() const { - struct itimerspec curr_value = {}; - if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) { - return PosixError(errno, "timer_gettime"); - } - MaybeSave(); - return curr_value; - } - - PosixErrorOr<int> Overruns() const { - int rv = syscall(SYS_timer_getoverrun, id_); - if (rv < 0) { - return PosixError(errno, "timer_getoverrun"); - } - MaybeSave(); - return rv; - } - - private: - void set_id(int id) { id_ = std::max(id, -1); } - - // Kernel timer_t is int; glibc timer_t is void*. - int id_ = -1; -}; - -PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, - const struct sigevent& sev) { - int timerid; - int ret = syscall(SYS_timer_create, clockid, &sev, &timerid); - if (ret < 0) { - return PosixError(errno, "timer_create"); - } - if (ret > 0) { - return PosixError(EINVAL, "timer_create should never return positive"); - } - MaybeSave(); - return IntervalTimer(timerid); -} - // See timerfd.cc:TimerSlack() for rationale. constexpr absl::Duration kTimerSlack = absl::Milliseconds(500); diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 1a7673317..bc5bd9218 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -679,6 +679,43 @@ TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) { SyscallSucceedsWithValue(sizeof(buf))); } +TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) { + ASSERT_NO_ERRNO(BindLoopback()); + // Close the socket to release the port so that we get an ICMP error. + ASSERT_THAT(close(bind_.release()), SyscallSucceeds()); + + // Connect to loopback:bind_addr_ which should *hopefully* not be bound by an + // UDP socket. There is no easy way to ensure that the UDP port is not bound + // by another conncurrently running test. *This is potentially flaky*. + ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); + + char buf[512]; + EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + constexpr int kTimeout = 1000; + // Poll to make sure we get the ICMP error back before issuing more writes. + struct pollfd pfd = {sock_.get(), POLLERR, 0}; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); + + // Next write should fail with ECONNREFUSED due to the ICMP error generated in + // response to the previous write. + ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), + SyscallFailsWithErrno(ECONNREFUSED)); + + // The next write should succeed again since the last write call would have + // retrieved and cleared the socket error. + ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), SyscallSucceeds()); + + // Poll to make sure we get the ICMP error back before issuing more writes. + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); + + // Next write should fail with ECONNREFUSED due to the ICMP error generated in + // response to the previous write. + ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), + SyscallFailsWithErrno(ECONNREFUSED)); +} + TEST_P(UdpSocketTest, ZerolengthWriteAllowed) { // TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes. SKIP_IF(IsRunningWithHostinet()); @@ -838,7 +875,7 @@ TEST_P(UdpSocketTest, ReceiveBeforeConnect) { // Receive the data. It works because it was sent before the connect. char received[sizeof(buf)]; EXPECT_THAT( - RecvMsgTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/), + RecvTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/), IsPosixErrorOkAndHolds(sizeof(received))); EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); @@ -928,9 +965,8 @@ TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) { SyscallSucceedsWithValue(1)); // We should get the data even though read has been shutdown. - EXPECT_THAT( - RecvMsgTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/), - IsPosixErrorOkAndHolds(2)); + EXPECT_THAT(RecvTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/), + IsPosixErrorOkAndHolds(2)); // Because we read less than the entire packet length, since it's a packet // based socket any subsequent reads should return EWOULDBLOCK. @@ -1698,8 +1734,8 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) { sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), SyscallSucceedsWithValue(buf.size())); std::vector<char> received(buf.size()); - EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(), - 1 /*timeout*/), + EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(), + 1 /*timeout*/), IsPosixErrorOkAndHolds(received.size())); } @@ -1714,8 +1750,8 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) { SyscallSucceedsWithValue(buf.size())); std::vector<char> received(buf.size()); - ASSERT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(), - 1 /*timeout*/), + ASSERT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(), + 1 /*timeout*/), IsPosixErrorOkAndHolds(received.size())); } } @@ -1785,8 +1821,8 @@ TEST_P(UdpSocketTest, RecvBufLimits) { for (int i = 0; i < sent - 1; i++) { // Receive the data. std::vector<char> received(buf.size()); - EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(), - 1 /*timeout*/), + EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(), + 1 /*timeout*/), IsPosixErrorOkAndHolds(received.size())); EXPECT_EQ(memcmp(buf.data(), received.data(), buf.size()), 0); } diff --git a/test/util/BUILD b/test/util/BUILD index 26c2b6a2f..1b028a477 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -155,6 +155,10 @@ cc_library( ], hdrs = ["save_util.h"], defines = select_system(), + deps = [ + ":logging", + "@com_google_absl//absl/types:optional", + ], ) cc_library( diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc index cebf7e0ac..deed0c05b 100644 --- a/test/util/posix_error.cc +++ b/test/util/posix_error.cc @@ -87,7 +87,7 @@ bool PosixErrorIsMatcherCommonImpl::MatchAndExplain( return false; } - if (!message_matcher_.Matches(error.error_message())) { + if (!message_matcher_.Matches(error.message())) { return false; } diff --git a/test/util/posix_error.h b/test/util/posix_error.h index ad666bce0..b634a7f78 100644 --- a/test/util/posix_error.h +++ b/test/util/posix_error.h @@ -26,11 +26,6 @@ namespace gvisor { namespace testing { -class PosixErrorIsMatcherCommonImpl; - -template <typename T> -class PosixErrorOr; - class ABSL_MUST_USE_RESULT PosixError { public: PosixError() {} @@ -49,7 +44,8 @@ class ABSL_MUST_USE_RESULT PosixError { // PosixErrorOr. const PosixError& error() const { return *this; } - std::string error_message() const { return msg_; } + int errno_value() const { return errno_; } + std::string message() const { return msg_; } // ToString produces a full string representation of this posix error // including the printable representation of the errno and the error message. @@ -61,14 +57,8 @@ class ABSL_MUST_USE_RESULT PosixError { void IgnoreError() const {} private: - int errno_value() const { return errno_; } int errno_ = 0; std::string msg_; - - friend class PosixErrorIsMatcherCommonImpl; - - template <typename T> - friend class PosixErrorOr; }; template <typename T> @@ -94,15 +84,12 @@ class ABSL_MUST_USE_RESULT PosixErrorOr { template <typename U> PosixErrorOr& operator=(PosixErrorOr<U> other); - // Return a reference to the error or NoError(). - PosixError error() const; - - // Returns this->error().error_message(); - std::string error_message() const; - // Returns true if this PosixErrorOr contains some T. bool ok() const; + // Return a copy of the contained PosixError or NoError(). + PosixError error() const; + // Returns a reference to our current value, or CHECK-fails if !this->ok(). const T& ValueOrDie() const&; T& ValueOrDie() &; @@ -115,7 +102,6 @@ class ABSL_MUST_USE_RESULT PosixErrorOr { void IgnoreError() const {} private: - int errno_value() const; absl::variant<T, PosixError> value_; friend class PosixErrorIsMatcherCommonImpl; @@ -171,16 +157,6 @@ PosixError PosixErrorOr<T>::error() const { } template <typename T> -int PosixErrorOr<T>::errno_value() const { - return error().errno_value(); -} - -template <typename T> -std::string PosixErrorOr<T>::error_message() const { - return error().error_message(); -} - -template <typename T> bool PosixErrorOr<T>::ok() const { return absl::holds_alternative<T>(value_); } diff --git a/test/util/save_util.cc b/test/util/save_util.cc index 384d626f0..59d47e06e 100644 --- a/test/util/save_util.cc +++ b/test/util/save_util.cc @@ -21,35 +21,47 @@ #include <atomic> #include <cerrno> -#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST" +#include "absl/types/optional.h" namespace gvisor { namespace testing { namespace { -enum class CooperativeSaveMode { - kUnknown = 0, // cooperative_save_mode is statically-initialized to 0 - kAvailable, - kNotAvailable, -}; - -std::atomic<CooperativeSaveMode> cooperative_save_mode; - -bool CooperativeSaveEnabled() { - auto mode = cooperative_save_mode.load(); - if (mode == CooperativeSaveMode::kUnknown) { - mode = (getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr) - ? CooperativeSaveMode::kAvailable - : CooperativeSaveMode::kNotAvailable; - cooperative_save_mode.store(mode); +std::atomic<absl::optional<bool>> cooperative_save_present; +std::atomic<absl::optional<bool>> random_save_present; + +bool CooperativeSavePresent() { + auto present = cooperative_save_present.load(); + if (!present.has_value()) { + present = getenv("GVISOR_COOPERATIVE_SAVE_TEST") != nullptr; + cooperative_save_present.store(present); + } + return present.value(); +} + +bool RandomSavePresent() { + auto present = random_save_present.load(); + if (!present.has_value()) { + present = getenv("GVISOR_RANDOM_SAVE_TEST") != nullptr; + random_save_present.store(present); } - return mode == CooperativeSaveMode::kAvailable; + return present.value(); } std::atomic<int> save_disable; } // namespace +bool IsRunningWithSaveRestore() { + return CooperativeSavePresent() || RandomSavePresent(); +} + +void MaybeSave() { + if (CooperativeSavePresent() && save_disable.load() == 0) { + internal::DoCooperativeSave(); + } +} + DisableSave::DisableSave() { save_disable++; } DisableSave::~DisableSave() { reset(); } @@ -61,11 +73,5 @@ void DisableSave::reset() { } } -namespace internal { -bool ShouldSave() { - return CooperativeSaveEnabled() && (save_disable.load() == 0); -} -} // namespace internal - } // namespace testing } // namespace gvisor diff --git a/test/util/save_util.h b/test/util/save_util.h index bddad6120..e7218ae88 100644 --- a/test/util/save_util.h +++ b/test/util/save_util.h @@ -17,9 +17,17 @@ namespace gvisor { namespace testing { -// Disable save prevents saving while the given function executes. + +// Returns true if the environment in which the calling process is executing +// allows the test to be checkpointed and restored during execution. +bool IsRunningWithSaveRestore(); + +// May perform a co-operative save cycle. // -// This lasts the duration of the object, unless reset is called. +// errno is guaranteed to be preserved. +void MaybeSave(); + +// Causes MaybeSave to become a no-op until destroyed or reset. class DisableSave { public: DisableSave(); @@ -37,13 +45,13 @@ class DisableSave { bool reset_ = false; }; -// May perform a co-operative save cycle. +namespace internal { + +// Causes a co-operative save cycle to occur. // // errno is guaranteed to be preserved. -void MaybeSave(); +void DoCooperativeSave(); -namespace internal { -bool ShouldSave(); } // namespace internal } // namespace testing diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc index d0aea8e6a..57431b3ea 100644 --- a/test/util/save_util_linux.cc +++ b/test/util/save_util_linux.cc @@ -30,20 +30,20 @@ namespace gvisor { namespace testing { - -void MaybeSave() { - if (internal::ShouldSave()) { - int orig_errno = errno; - // We use it to trigger saving the sentry state - // when this syscall is called. - // Notice: this needs to be a valid syscall - // that is not used in any of the syscall tests. - syscall(SYS_TRIGGER_SAVE, nullptr, 0); - errno = orig_errno; - } +namespace internal { + +void DoCooperativeSave() { + int orig_errno = errno; + // We use it to trigger saving the sentry state + // when this syscall is called. + // Notice: this needs to be a valid syscall + // that is not used in any of the syscall tests. + syscall(SYS_TRIGGER_SAVE, nullptr, 0); + errno = orig_errno; } +} // namespace internal } // namespace testing } // namespace gvisor -#endif +#endif // __linux__ diff --git a/test/util/save_util_other.cc b/test/util/save_util_other.cc index 931af2c29..7749ded76 100644 --- a/test/util/save_util_other.cc +++ b/test/util/save_util_other.cc @@ -14,13 +14,17 @@ #ifndef __linux__ +#include "test/util/logging.h" + namespace gvisor { namespace testing { +namespace internal { -void MaybeSave() { - // Saving is never available in a non-linux environment. +void DoCooperativeSave() { + TEST_CHECK_MSG(false, "DoCooperativeSave not implemented"); } +} // namespace internal } // namespace testing } // namespace gvisor diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc index 43a26b0d3..75cfc4f40 100644 --- a/test/util/timer_util.cc +++ b/test/util/timer_util.cc @@ -23,5 +23,23 @@ absl::Time Now(clockid_t id) { return absl::TimeFromTimespec(now); } +#ifdef __linux__ + +PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, + const struct sigevent& sev) { + int timerid; + int ret = syscall(SYS_timer_create, clockid, &sev, &timerid); + if (ret < 0) { + return PosixError(errno, "timer_create"); + } + if (ret > 0) { + return PosixError(EINVAL, "timer_create should never return positive"); + } + MaybeSave(); + return IntervalTimer(timerid); +} + +#endif // __linux__ + } // namespace testing } // namespace gvisor diff --git a/test/util/timer_util.h b/test/util/timer_util.h index 31aea4fc6..926e6632f 100644 --- a/test/util/timer_util.h +++ b/test/util/timer_util.h @@ -16,6 +16,9 @@ #define GVISOR_TEST_UTIL_TIMER_UTIL_H_ #include <errno.h> +#ifdef __linux__ +#include <sys/syscall.h> +#endif #include <sys/time.h> #include <functional> @@ -30,6 +33,9 @@ namespace gvisor { namespace testing { +// Returns the current time. +absl::Time Now(clockid_t id); + // MonotonicTimer is a simple timer that uses a monotonic clock. class MonotonicTimer { public: @@ -65,8 +71,92 @@ inline PosixErrorOr<Cleanup> ScopedItimer(int which, })); } -// Returns the current time. -absl::Time Now(clockid_t id); +#ifdef __linux__ + +// RAII type for a kernel "POSIX" interval timer. (The kernel provides system +// calls such as timer_create that behave very similarly, but not identically, +// to those described by timer_create(2); in particular, the kernel does not +// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on +// these kernel interval timers.) +// +// Compare implementation to FileDescriptor. +class IntervalTimer { + public: + IntervalTimer() = default; + + explicit IntervalTimer(int id) { set_id(id); } + + IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {} + + IntervalTimer& operator=(IntervalTimer&& orig) { + if (this == &orig) return *this; + reset(orig.release()); + return *this; + } + + IntervalTimer(const IntervalTimer& other) = delete; + IntervalTimer& operator=(const IntervalTimer& other) = delete; + + ~IntervalTimer() { reset(); } + + int get() const { return id_; } + + int release() { + int const id = id_; + id_ = -1; + return id; + } + + void reset() { reset(-1); } + + void reset(int id) { + if (id_ >= 0) { + TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0); + MaybeSave(); + } + set_id(id); + } + + PosixErrorOr<struct itimerspec> Set( + int flags, const struct itimerspec& new_value) const { + struct itimerspec old_value = {}; + if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) { + return PosixError(errno, "timer_settime"); + } + MaybeSave(); + return old_value; + } + + PosixErrorOr<struct itimerspec> Get() const { + struct itimerspec curr_value = {}; + if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) { + return PosixError(errno, "timer_gettime"); + } + MaybeSave(); + return curr_value; + } + + PosixErrorOr<int> Overruns() const { + int rv = syscall(SYS_timer_getoverrun, id_); + if (rv < 0) { + return PosixError(errno, "timer_getoverrun"); + } + MaybeSave(); + return rv; + } + + private: + void set_id(int id) { id_ = std::max(id, -1); } + + // Kernel timer_t is int; glibc timer_t is void*. + int id_ = -1; +}; + +// A wrapper around timer_create(2). +PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, + const struct sigevent& sev); + +#endif // __linux__ } // namespace testing } // namespace gvisor |