diff options
Diffstat (limited to 'test/syscalls/linux')
36 files changed, 1080 insertions, 450 deletions
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index de753fc4e..d9dbe2267 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1667,12 +1667,14 @@ cc_binary( "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:fs_util", + "@com_google_absl//absl/container:node_hash_set", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", gtest, "//test/util:memory_util", "//test/util:posix_error", + "//test/util:proc_util", "//test/util:temp_path", "//test/util:test_util", "//test/util:thread_util", @@ -2412,6 +2414,7 @@ cc_library( ":socket_test_util", "@com_google_absl//absl/memory", gtest, + "//test/util:posix_error", "//test/util:test_util", ], alwayslink = 1, @@ -2430,6 +2433,7 @@ cc_library( ":socket_netlink_route_util", ":socket_test_util", "//test/util:capability_util", + "//test/util:cleanup", gtest, ], alwayslink = 1, diff --git a/test/syscalls/linux/fallocate.cc b/test/syscalls/linux/fallocate.cc index cabc2b751..edd23e063 100644 --- a/test/syscalls/linux/fallocate.cc +++ b/test/syscalls/linux/fallocate.cc @@ -179,6 +179,12 @@ TEST_F(AllocateTest, FallocateOtherFDs) { auto sock0 = FileDescriptor(socks[0]); auto sock1 = FileDescriptor(socks[1]); EXPECT_THAT(fallocate(sock0.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV)); + + int pipefds[2]; + ASSERT_THAT(pipe(pipefds), SyscallSucceeds()); + EXPECT_THAT(fallocate(pipefds[1], 0, 0, 10), SyscallFailsWithErrno(ESPIPE)); + close(pipefds[0]); + close(pipefds[1]); } } // namespace diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc index a5c421118..e4392a450 100644 --- a/test/syscalls/linux/inotify.cc +++ b/test/syscalls/linux/inotify.cc @@ -465,7 +465,9 @@ TEST(Inotify, ConcurrentFileDeletionAndWatchRemoval) { for (int i = 0; i < 100; ++i) { FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT, S_IRUSR | S_IWUSR)); - file_fd.reset(); // Close before unlinking (although save is disabled). + // Close before unlinking (although S/R is disabled). Some filesystems + // cannot restore an open fd on an unlinked file. + file_fd.reset(); EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds()); } }; @@ -1256,10 +1258,7 @@ TEST(Inotify, MknodGeneratesCreateEvent) { InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); const TempPath file1(root.path() + "/file1"); - const int rc = mknod(file1.path().c_str(), S_IFREG, 0); - // mknod(2) is only supported on tmpfs in the sandbox. - SKIP_IF(IsRunningOnGvisor() && rc != 0); - ASSERT_THAT(rc, SyscallSucceeds()); + ASSERT_THAT(mknod(file1.path().c_str(), S_IFREG, 0), SyscallSucceeds()); const std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); @@ -1289,6 +1288,10 @@ TEST(Inotify, SymlinkGeneratesCreateEvent) { } TEST(Inotify, LinkGeneratesAttribAndCreateEvents) { + // Inotify does not work properly with hard links in gofer and overlay fs. + SKIP_IF(IsRunningOnGvisor() && + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); + const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); @@ -1301,11 +1304,8 @@ TEST(Inotify, LinkGeneratesAttribAndCreateEvents) { const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - const int rc = link(file1.path().c_str(), link1.path().c_str()); - // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox. - SKIP_IF(IsRunningOnGvisor() && rc != 0 && - (errno == EPERM || errno == ENOENT)); - ASSERT_THAT(rc, SyscallSucceeds()); + ASSERT_THAT(link(file1.path().c_str(), link1.path().c_str()), + SyscallSucceeds()); const std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); @@ -1334,68 +1334,70 @@ TEST(Inotify, UtimesGeneratesAttribEvent) { } TEST(Inotify, HardlinksReuseSameWatch) { + // Inotify does not work properly with hard links in gofer and overlay fs. + SKIP_IF(IsRunningOnGvisor() && + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); + const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file1 = + TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - TempPath link1(root.path() + "/link1"); - const int rc = link(file1.path().c_str(), link1.path().c_str()); - // link(2) is only supported on tmpfs in the sandbox. - SKIP_IF(IsRunningOnGvisor() && rc != 0 && - (errno == EPERM || errno == ENOENT)); - ASSERT_THAT(rc, SyscallSucceeds()); + + TempPath file2(root.path() + "/file2"); + ASSERT_THAT(link(file.path().c_str(), file2.path().c_str()), + SyscallSucceeds()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - const int link1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), link1.path(), IN_ALL_EVENTS)); + const int file_wd = ASSERT_NO_ERRNO_AND_VALUE( + InotifyAddWatch(fd.get(), file.path(), IN_ALL_EVENTS)); + const int file2_wd = ASSERT_NO_ERRNO_AND_VALUE( + InotifyAddWatch(fd.get(), file2.path(), IN_ALL_EVENTS)); // The watch descriptors for watches on different links to the same file // should be identical. - EXPECT_NE(root_wd, file1_wd); - EXPECT_EQ(file1_wd, link1_wd); + EXPECT_NE(root_wd, file_wd); + EXPECT_EQ(file_wd, file2_wd); - FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); + FileDescriptor file_fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); ASSERT_THAT(events, - AreUnordered({Event(IN_OPEN, root_wd, Basename(file1.path())), - Event(IN_OPEN, file1_wd)})); + AreUnordered({Event(IN_OPEN, root_wd, Basename(file.path())), + Event(IN_OPEN, file_wd)})); // For the next step, we want to ensure all fds to the file are closed. Do // that now and drain the resulting events. - file1_fd.reset(); + file_fd.reset(); events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); ASSERT_THAT( events, - AreUnordered({Event(IN_CLOSE_WRITE, root_wd, Basename(file1.path())), - Event(IN_CLOSE_WRITE, file1_wd)})); + AreUnordered({Event(IN_CLOSE_WRITE, root_wd, Basename(file.path())), + Event(IN_CLOSE_WRITE, file_wd)})); // Try removing the link and let's see what events show up. Note that after // this, we still have a link to the file so the watch shouldn't be // automatically removed. - const std::string link1_path = link1.reset(); + const std::string file2_path = file2.reset(); events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); ASSERT_THAT(events, - AreUnordered({Event(IN_ATTRIB, link1_wd), - Event(IN_DELETE, root_wd, Basename(link1_path))})); + AreUnordered({Event(IN_ATTRIB, file2_wd), + Event(IN_DELETE, root_wd, Basename(file2_path))})); // Now remove the other link. Since this is the last link to the file, the // watch should be automatically removed. - const std::string file1_path = file1.reset(); + const std::string file_path = file.reset(); events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); ASSERT_THAT( events, - AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd), - Event(IN_IGNORED, file1_wd), - Event(IN_DELETE, root_wd, Basename(file1_path))})); + AreUnordered({Event(IN_ATTRIB, file_wd), Event(IN_DELETE_SELF, file_wd), + Event(IN_IGNORED, file_wd), + Event(IN_DELETE, root_wd, Basename(file_path))})); } // Calling mkdir within "parent/child" should generate an event for child, but @@ -1806,17 +1808,17 @@ TEST(Inotify, SpliceOnInotifyFD) { // Watches on a parent should not be triggered by actions on a hard link to one // of its children that has a different parent. TEST(Inotify, LinkOnOtherParent) { + // Inotify does not work properly with hard links in gofer and overlay fs. + SKIP_IF(IsRunningOnGvisor() && + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); + const TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const TempPath dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); std::string link_path = NewTempAbsPathInDir(dir2.path()); - const int rc = link(file.path().c_str(), link_path.c_str()); - // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox. - SKIP_IF(IsRunningOnGvisor() && rc != 0 && - (errno == EPERM || errno == ENOENT)); - ASSERT_THAT(rc, SyscallSucceeds()); + ASSERT_THAT(link(file.path().c_str(), link_path.c_str()), SyscallSucceeds()); const FileDescriptor inotify_fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); @@ -1825,13 +1827,18 @@ TEST(Inotify, LinkOnOtherParent) { // Perform various actions on the link outside of dir1, which should trigger // no inotify events. - const FileDescriptor fd = + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(link_path.c_str(), O_RDWR)); int val = 0; ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds()); ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds()); ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds()); + + // Close before unlinking; some filesystems cannot restore an open fd on an + // unlinked file. + fd.reset(); ASSERT_THAT(unlink(link_path.c_str()), SyscallSucceeds()); + const std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); EXPECT_THAT(events, Are({})); @@ -2055,21 +2062,21 @@ TEST(Inotify, ExcludeUnlinkDirectory_NoRandomSave) { // We need to disable S/R because there are filesystems where we cannot re-open // fds to an unlinked file across S/R, e.g. gofer-backed filesytems. TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) { - const DisableSave ds; + // Inotify does not work properly with hard links in gofer and overlay fs. + SKIP_IF(IsRunningOnGvisor() && + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); // TODO(gvisor.dev/issue/1624): This test fails on VFS1. SKIP_IF(IsRunningWithVFS1()); + const DisableSave ds; + const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); std::string path1 = file.path(); std::string path2 = NewTempAbsPathInDir(dir.path()); + ASSERT_THAT(link(path1.c_str(), path2.c_str()), SyscallSucceeds()); - const int rc = link(path1.c_str(), path2.c_str()); - // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox. - SKIP_IF(IsRunningOnGvisor() && rc != 0 && - (errno == EPERM || errno == ENOENT)); - ASSERT_THAT(rc, SyscallSucceeds()); const FileDescriptor fd1 = ASSERT_NO_ERRNO_AND_VALUE(Open(path1.c_str(), O_RDWR)); const FileDescriptor fd2 = @@ -2101,6 +2108,15 @@ TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) { // We need to disable S/R because there are filesystems where we cannot re-open // fds to an unlinked file across S/R, e.g. gofer-backed filesytems. TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) { + // TODO(gvisor.dev/issue/1624): Fails on VFS1. + SKIP_IF(IsRunningWithVFS1()); + + // NOTE(gvisor.dev/issue/3654): In the gofer filesystem, we do not allow + // setting attributes through an fd if the file at the open path has been + // deleted. + SKIP_IF(IsRunningOnGvisor() && + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); + const DisableSave ds; const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); @@ -2110,18 +2126,6 @@ TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR)); - // NOTE(b/157163751): Create another link before unlinking. This is needed for - // the gofer filesystem in gVisor, where open fds will not work once the link - // count hits zero. In VFS2, we end up skipping the gofer test anyway, because - // hard links are not supported for gofer fs. - if (IsRunningOnGvisor()) { - std::string link_path = NewTempAbsPath(); - const int rc = link(file.path().c_str(), link_path.c_str()); - // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox. - SKIP_IF(rc != 0 && (errno == EPERM || errno == ENOENT)); - ASSERT_THAT(rc, SyscallSucceeds()); - } - const FileDescriptor inotify_fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( diff --git a/test/syscalls/linux/ip6tables.cc b/test/syscalls/linux/ip6tables.cc index 78e1fa09d..de0a1c114 100644 --- a/test/syscalls/linux/ip6tables.cc +++ b/test/syscalls/linux/ip6tables.cc @@ -82,13 +82,37 @@ TEST(IP6TablesBasic, GetEntriesErrorPrecedence) { SyscallFailsWithErrno(EINVAL)); } +TEST(IP6TablesBasic, GetRevision) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW), + SyscallSucceeds()); + + struct xt_get_revision rev = { + .name = "REDIRECT", + .revision = 0, + }; + socklen_t rev_len = sizeof(rev); + + // Revision 0 exists. + EXPECT_THAT( + getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len), + SyscallSucceeds()); + EXPECT_EQ(rev.revision, 0); + + // Revisions > 0 don't exist. + rev.revision = 1; + EXPECT_THAT( + getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len), + SyscallFailsWithErrno(EPROTONOSUPPORT)); +} + // This tests the initial state of a machine with empty ip6tables via // getsockopt(IP6T_SO_GET_INFO). We don't have a guarantee that the iptables are // empty when running in native, but we can test that gVisor has the same // initial state that a newly-booted Linux machine would have. TEST(IP6TablesTest, InitialInfo) { - // TODO(gvisor.dev/issue/3549): Enable for ip6tables. - SKIP_IF(true); SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); FileDescriptor sock = @@ -132,8 +156,6 @@ TEST(IP6TablesTest, InitialInfo) { // are empty when running in native, but we can test that gVisor has the same // initial state that a newly-booted Linux machine would have. TEST(IP6TablesTest, InitialEntries) { - // TODO(gvisor.dev/issue/3549): Enable for ip6tables. - SKIP_IF(true); SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); FileDescriptor sock = diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc index 83b6a164a..7ee10bbde 100644 --- a/test/syscalls/linux/iptables.cc +++ b/test/syscalls/linux/iptables.cc @@ -117,6 +117,32 @@ TEST(IPTablesBasic, OriginalDstErrors) { SyscallFailsWithErrno(ENOTCONN)); } +TEST(IPTablesBasic, GetRevision) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), + SyscallSucceeds()); + + struct xt_get_revision rev = { + .name = "REDIRECT", + .revision = 0, + }; + socklen_t rev_len = sizeof(rev); + + // Revision 0 exists. + EXPECT_THAT( + getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len), + SyscallSucceeds()); + EXPECT_EQ(rev.revision, 0); + + // Revisions > 0 don't exist. + rev.revision = 1; + EXPECT_THAT( + getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len), + SyscallFailsWithErrno(EPROTONOSUPPORT)); +} + // Fixture for iptables tests. class IPTablesTest : public ::testing::Test { protected: diff --git a/test/syscalls/linux/kcov.cc b/test/syscalls/linux/kcov.cc index f3c30444e..6afcb4e75 100644 --- a/test/syscalls/linux/kcov.cc +++ b/test/syscalls/linux/kcov.cc @@ -36,12 +36,13 @@ TEST(KcovTest, Kcov) { constexpr int kSize = 4096; constexpr int KCOV_INIT_TRACE = 0x80086301; constexpr int KCOV_ENABLE = 0x6364; + constexpr int KCOV_DISABLE = 0x6365; int fd; ASSERT_THAT(fd = open("/sys/kernel/debug/kcov", O_RDWR), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - // Kcov not enabled. + // Kcov not available. SKIP_IF(errno == ENOENT); ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); @@ -62,6 +63,8 @@ TEST(KcovTest, Kcov) { // Verify that PCs are in the standard kernel range. EXPECT_GT(area[i], 0xffffffff7fffffffL); } + + ASSERT_THAT(ioctl(fd, KCOV_DISABLE, 0), SyscallSucceeds()); } } // namespace diff --git a/test/syscalls/linux/mkdir.cc b/test/syscalls/linux/mkdir.cc index 4036a9275..27758203d 100644 --- a/test/syscalls/linux/mkdir.cc +++ b/test/syscalls/linux/mkdir.cc @@ -82,6 +82,13 @@ TEST_F(MkdirTest, FailsOnDirWithoutWritePerms) { SyscallFailsWithErrno(EACCES)); } +TEST_F(MkdirTest, MkdirAtEmptyPath) { + ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds()); + auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dirname_, O_RDONLY | O_DIRECTORY, 0666)); + EXPECT_THAT(mkdirat(fd.get(), "", 0777), SyscallFailsWithErrno(ENOENT)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc index 2ba8c11b8..ae65d366b 100644 --- a/test/syscalls/linux/mknod.cc +++ b/test/syscalls/linux/mknod.cc @@ -105,11 +105,13 @@ TEST(MknodTest, UnimplementedTypesReturnError) { } TEST(MknodTest, Socket) { + SKIP_IF(IsRunningOnGvisor() && IsRunningWithVFS1()); + ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - SKIP_IF(IsRunningOnGvisor() && IsRunningWithVFS1()); + auto filename = NewTempRelPath(); - ASSERT_THAT(mknod("./file0", S_IFSOCK | S_IRUSR | S_IWUSR, 0), + ASSERT_THAT(mknod(filename.c_str(), S_IFSOCK | S_IRUSR | S_IWUSR, 0), SyscallSucceeds()); int sk; @@ -117,9 +119,10 @@ TEST(MknodTest, Socket) { FileDescriptor fd(sk); struct sockaddr_un addr = {.sun_family = AF_UNIX}; - absl::SNPrintF(addr.sun_path, sizeof(addr.sun_path), "./file0"); + absl::SNPrintF(addr.sun_path, sizeof(addr.sun_path), "%s", filename.c_str()); ASSERT_THAT(connect(sk, (struct sockaddr *)&addr, sizeof(addr)), SyscallFailsWithErrno(ECONNREFUSED)); + ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds()); } TEST(MknodTest, Fifo) { @@ -203,6 +206,14 @@ TEST(MknodTest, FifoTruncNoOp) { EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL)); } +TEST(MknodTest, MknodAtEmptyPath) { + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY, 0666)); + EXPECT_THAT(mknodat(fd.get(), "", S_IFREG | 0777, 0), + SyscallFailsWithErrno(ENOENT)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index 46b6f38db..3aab25b23 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -147,8 +147,15 @@ TEST(MountTest, UmountDetach) { // Unmount the tmpfs. mount.Release()(); - const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_EQ(before.st_ino, after2.st_ino); + // 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()))) { + const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); + EXPECT_EQ(before.st_ino, after2.st_ino); + } // Can still read file after unmounting. std::vector<char> buf(sizeof(kContents)); @@ -213,8 +220,15 @@ TEST(MountTest, MountTmpfs) { } // Now that dir is unmounted again, we should have the old inode back. - const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_EQ(before.st_ino, after.st_ino); + // 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()))) { + const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); + EXPECT_EQ(before.st_ino, after.st_ino); + } } TEST(MountTest, MountTmpfsMagicValIgnored) { diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc index 51eacf3f2..78c36f98f 100644 --- a/test/syscalls/linux/open_create.cc +++ b/test/syscalls/linux/open_create.cc @@ -88,21 +88,21 @@ TEST(CreateTest, CreateExclusively) { SyscallFailsWithErrno(EEXIST)); } -TEST(CreateTeast, CreatWithOTrunc) { +TEST(CreateTest, CreatWithOTrunc) { std::string dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); ASSERT_THAT(open(dirpath.c_str(), O_CREAT | O_TRUNC, 0666), SyscallFailsWithErrno(EISDIR)); } -TEST(CreateTeast, CreatDirWithOTruncAndReadOnly) { +TEST(CreateTest, CreatDirWithOTruncAndReadOnly) { std::string dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); ASSERT_THAT(open(dirpath.c_str(), O_CREAT | O_TRUNC | O_RDONLY, 0666), SyscallFailsWithErrno(EISDIR)); } -TEST(CreateTeast, CreatFileWithOTruncAndReadOnly) { +TEST(CreateTest, CreatFileWithOTruncAndReadOnly) { std::string dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncfile"); int dirfd; ASSERT_THAT(dirfd = open(dirpath.c_str(), O_RDWR | O_CREAT, 0666), @@ -149,6 +149,116 @@ TEST(CreateTest, OpenCreateROThenRW) { EXPECT_THAT(WriteFd(fd2.get(), &c, 1), SyscallSucceedsWithValue(1)); } +TEST(CreateTest, ChmodReadToWriteBetweenOpens_NoRandomSave) { + // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to + // override file read/write permissions. CAP_DAC_READ_SEARCH needs to be + // cleared for the same reason. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); + + const TempPath file = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0400)); + + const FileDescriptor rfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + + // Cannot restore after making permissions more restrictive. + const DisableSave ds; + ASSERT_THAT(fchmod(rfd.get(), 0200), SyscallSucceeds()); + + EXPECT_THAT(open(file.path().c_str(), O_RDONLY), + SyscallFailsWithErrno(EACCES)); + + const FileDescriptor wfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); + + char c = 'x'; + EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + c = 0; + EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + EXPECT_EQ(c, 'x'); +} + +TEST(CreateTest, ChmodWriteToReadBetweenOpens_NoRandomSave) { + // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to + // override file read/write permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + + const TempPath file = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0200)); + + const FileDescriptor wfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); + + // Cannot restore after making permissions more restrictive. + const DisableSave ds; + ASSERT_THAT(fchmod(wfd.get(), 0400), SyscallSucceeds()); + + EXPECT_THAT(open(file.path().c_str(), O_WRONLY), + SyscallFailsWithErrno(EACCES)); + + const FileDescriptor rfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + + char c = 'x'; + EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + c = 0; + EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + EXPECT_EQ(c, 'x'); +} + +TEST(CreateTest, CreateWithReadFlagNotAllowedByMode_NoRandomSave) { + // The only time we can open a file with flags forbidden by its permissions + // is when we are creating the file. We cannot re-open with the same flags, + // so we cannot restore an fd obtained from such an operation. + const DisableSave ds; + + // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to + // override file read/write permissions. CAP_DAC_READ_SEARCH needs to be + // cleared for the same reason. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); + + // Create and open a file with read flag but without read permissions. + const std::string path = NewTempAbsPath(); + const FileDescriptor rfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CREAT | O_RDONLY, 0222)); + + EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); + const FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_WRONLY)); + + char c = 'x'; + EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + c = 0; + EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + EXPECT_EQ(c, 'x'); +} + +TEST(CreateTest, CreateWithWriteFlagNotAllowedByMode_NoRandomSave) { + // The only time we can open a file with flags forbidden by its permissions + // is when we are creating the file. We cannot re-open with the same flags, + // so we cannot restore an fd obtained from such an operation. + const DisableSave ds; + + // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to + // override file read/write permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + + // Create and open a file with write flag but without write permissions. + const std::string path = NewTempAbsPath(); + const FileDescriptor wfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CREAT | O_WRONLY, 0444)); + + EXPECT_THAT(open(path.c_str(), O_WRONLY), SyscallFailsWithErrno(EACCES)); + const FileDescriptor rfd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY)); + + char c = 'x'; + EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + c = 0; + EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); + EXPECT_EQ(c, 'x'); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc index f3c1d6bc9..b558e3a01 100644 --- a/test/syscalls/linux/packet_socket_raw.cc +++ b/test/syscalls/linux/packet_socket_raw.cc @@ -643,6 +643,27 @@ TEST_P(RawPacketTest, GetSocketDetachFilter) { SyscallFailsWithErrno(ENOPROTOOPT)); } +TEST_P(RawPacketTest, SetAndGetSocketLinger) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int level = SOL_SOCKET; + int type = SO_LINGER; + + struct linger sl; + sl.l_onoff = 1; + sl.l_linger = 5; + ASSERT_THAT(setsockopt(s_, level, type, &sl, sizeof(sl)), + SyscallSucceedsWithValue(0)); + + struct linger got_linger = {}; + socklen_t length = sizeof(sl); + ASSERT_THAT(getsockopt(s_, level, type, &got_linger, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got_linger)); + EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest, ::testing::Values(ETH_P_IP, ETH_P_ALL)); diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc index 04c5161f5..f675dc430 100644 --- a/test/syscalls/linux/prctl.cc +++ b/test/syscalls/linux/prctl.cc @@ -153,7 +153,7 @@ TEST(PrctlTest, PDeathSig) { // Enable tracing, then raise SIGSTOP and expect our parent to suppress // it. TEST_CHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) >= 0); - raise(SIGSTOP); + TEST_CHECK(raise(SIGSTOP) == 0); // Sleep until killed by our parent death signal. sleep(3) is // async-signal-safe, absl::SleepFor isn't. while (true) { diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index b73189e55..e8fcc4439 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -47,6 +47,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/container/node_hash_set.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" @@ -63,6 +64,7 @@ #include "test/util/fs_util.h" #include "test/util/memory_util.h" #include "test/util/posix_error.h" +#include "test/util/proc_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -672,6 +674,23 @@ TEST(ProcSelfMaps, Mprotect) { 3 * kPageSize, PROT_READ))); } +TEST(ProcSelfMaps, SharedAnon) { + const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(kPageSize, PROT_READ, MAP_SHARED | MAP_ANONYMOUS)); + + const auto proc_self_maps = + ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); + for (const auto& line : absl::StrSplit(proc_self_maps, '\n')) { + const auto entry = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMapsLine(line)); + if (entry.start <= m.addr() && m.addr() < entry.end) { + // cf. proc(5), "/proc/[pid]/map_files/" + EXPECT_EQ(entry.filename, "/dev/zero (deleted)"); + return; + } + } + FAIL() << "no maps entry containing mapping at " << m.ptr(); +} + TEST(ProcSelfFd, OpenFd) { int pipe_fds[2]; ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds()); @@ -694,6 +713,30 @@ TEST(ProcSelfFd, OpenFd) { ASSERT_THAT(close(pipe_fds[1]), SyscallSucceeds()); } +static void CheckFdDirGetdentsDuplicates(const std::string& path) { + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_RDONLY | O_DIRECTORY)); + // Open a FD whose value is supposed to be much larger than + // the number of FDs opened by current process. + auto newfd = fcntl(fd.get(), F_DUPFD, 1024); + EXPECT_GE(newfd, 1024); + auto fd_closer = Cleanup([newfd]() { close(newfd); }); + auto fd_files = ASSERT_NO_ERRNO_AND_VALUE(ListDir(path.c_str(), false)); + absl::node_hash_set<std::string> fd_files_dedup(fd_files.begin(), + fd_files.end()); + EXPECT_EQ(fd_files.size(), fd_files_dedup.size()); +} + +// This is a regression test for gvisor.dev/issues/3894 +TEST(ProcSelfFd, GetdentsDuplicates) { + CheckFdDirGetdentsDuplicates("/proc/self/fd"); +} + +// This is a regression test for gvisor.dev/issues/3894 +TEST(ProcSelfFdInfo, GetdentsDuplicates) { + CheckFdDirGetdentsDuplicates("/proc/self/fdinfo"); +} + TEST(ProcSelfFdInfo, CorrectFds) { // Make sure there is at least one open file. auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); @@ -737,8 +780,12 @@ TEST(ProcSelfFdInfo, Flags) { } TEST(ProcSelfExe, Absolute) { - auto exe = ASSERT_NO_ERRNO_AND_VALUE( - ReadLink(absl::StrCat("/proc/", getpid(), "/exe"))); + auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe")); + EXPECT_EQ(exe[0], '/'); +} + +TEST(ProcSelfCwd, Absolute) { + auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/cwd")); EXPECT_EQ(exe[0], '/'); } @@ -773,17 +820,12 @@ TEST(ProcCpuinfo, DeniesWriteNonRoot) { constexpr int kNobody = 65534; EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES)); - // TODO(gvisor.dev/issue/1193): Properly support setting size attributes in - // kernfs. - if (!IsRunningOnGvisor() || IsRunningWithVFS1()) { - EXPECT_THAT(truncate("/proc/cpuinfo", 123), - SyscallFailsWithErrno(EACCES)); - } + EXPECT_THAT(truncate("/proc/cpuinfo", 123), SyscallFailsWithErrno(EACCES)); }); } // With root privileges, it is possible to open /proc/cpuinfo with write mode, -// but all write operations will return EIO. +// but all write operations should fail. TEST(ProcCpuinfo, DeniesWriteRoot) { // VFS1 does not behave differently for root/non-root. SKIP_IF(IsRunningWithVFS1()); @@ -792,16 +834,10 @@ TEST(ProcCpuinfo, DeniesWriteRoot) { int fd; EXPECT_THAT(fd = open("/proc/cpuinfo", O_WRONLY), SyscallSucceeds()); if (fd > 0) { - EXPECT_THAT(write(fd, "x", 1), SyscallFailsWithErrno(EIO)); - EXPECT_THAT(pwrite(fd, "x", 1, 123), SyscallFailsWithErrno(EIO)); - } - // TODO(gvisor.dev/issue/1193): Properly support setting size attributes in - // kernfs. - if (!IsRunningOnGvisor() || IsRunningWithVFS1()) { - if (fd > 0) { - EXPECT_THAT(ftruncate(fd, 123), SyscallFailsWithErrno(EIO)); - } - EXPECT_THAT(truncate("/proc/cpuinfo", 123), SyscallFailsWithErrno(EIO)); + // Truncate is not tested--it may succeed on some kernels without doing + // anything. + EXPECT_THAT(write(fd, "x", 1), SyscallFails()); + EXPECT_THAT(pwrite(fd, "x", 1, 123), SyscallFails()); } } @@ -1441,6 +1477,16 @@ TEST(ProcPidExe, Subprocess) { EXPECT_EQ(actual, expected_absolute_path); } +// /proc/PID/cwd points to the correct directory. +TEST(ProcPidCwd, Subprocess) { + auto want = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); + + char got[PATH_MAX + 1] = {}; + ASSERT_THAT(ReadlinkWhileRunning("cwd", got, sizeof(got)), + SyscallSucceedsWithValue(Gt(0))); + EXPECT_EQ(got, want); +} + // Test whether /proc/PID/ files can be read for a running process. TEST(ProcPidFile, SubprocessRunning) { char buf[1]; diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc index 4fab097f4..23677e296 100644 --- a/test/syscalls/linux/proc_net.cc +++ b/test/syscalls/linux/proc_net.cc @@ -39,6 +39,7 @@ namespace testing { namespace { constexpr const char kProcNet[] = "/proc/net"; +constexpr const char kIpForward[] = "/proc/sys/net/ipv4/ip_forward"; TEST(ProcNetSymlinkTarget, FileMode) { struct stat s; @@ -515,6 +516,46 @@ TEST(ProcSysNetIpv4Recovery, CanReadAndWrite) { SyscallSucceedsWithValue(sizeof(kMessage))); EXPECT_EQ(strcmp(buf, "100\n"), 0); } + +TEST(ProcSysNetIpv4IpForward, Exists) { + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kIpForward, O_RDONLY)); +} + +TEST(ProcSysNetIpv4IpForward, DefaultValueEqZero) { + // Test is only valid in sandbox. Not hermetic in native tests + // running on a arbitrary machine. + SKIP_IF(!IsRunningOnGvisor()); + auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kIpForward, O_RDONLY)); + + char buf = 101; + EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + EXPECT_EQ(buf, '0') << "unexpected ip_forward: " << buf; +} + +TEST(ProcSysNetIpv4IpForward, CanReadAndWrite) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); + + auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kIpForward, O_RDWR)); + + char buf; + EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + EXPECT_TRUE(buf == '0' || buf == '1') << "unexpected ip_forward: " << buf; + + // constexpr char to_write = '1'; + char to_write = (buf == '1') ? '0' : '1'; + EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0), + SyscallSucceedsWithValue(sizeof(to_write))); + + buf = 0; + EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + EXPECT_EQ(buf, to_write); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc index 2e4ab6ca8..0b174e2be 100644 --- a/test/syscalls/linux/pty.cc +++ b/test/syscalls/linux/pty.cc @@ -71,7 +71,7 @@ constexpr absl::Duration kTimeout = absl::Seconds(20); // The maximum line size in bytes returned per read from a pty file. constexpr int kMaxLineSize = 4096; -constexpr char kMainPath[] = "/dev/ptmx"; +constexpr char kMasterPath[] = "/dev/ptmx"; // glibc defines its own, different, version of struct termios. We care about // what the kernel does, not glibc. @@ -388,22 +388,22 @@ PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count, TEST(PtyTrunc, Truncate) { // Opening PTYs with O_TRUNC shouldn't cause an error, but calls to // (f)truncate should. - FileDescriptor main = - ASSERT_NO_ERRNO_AND_VALUE(Open(kMainPath, O_RDWR | O_TRUNC)); - int n = ASSERT_NO_ERRNO_AND_VALUE(ReplicaID(main)); + FileDescriptor master = + ASSERT_NO_ERRNO_AND_VALUE(Open(kMasterPath, O_RDWR | O_TRUNC)); + int n = ASSERT_NO_ERRNO_AND_VALUE(ReplicaID(master)); std::string spath = absl::StrCat("/dev/pts/", n); FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(Open(spath, O_RDWR | O_NONBLOCK | O_TRUNC)); - EXPECT_THAT(truncate(kMainPath, 0), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(truncate(kMasterPath, 0), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(truncate(spath.c_str(), 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(ftruncate(main.get(), 0), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(ftruncate(master.get(), 0), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(ftruncate(replica.get(), 0), SyscallFailsWithErrno(EINVAL)); } -TEST(BasicPtyTest, StatUnopenedMain) { +TEST(BasicPtyTest, StatUnopenedMaster) { struct stat s; - ASSERT_THAT(stat(kMainPath, &s), SyscallSucceeds()); + ASSERT_THAT(stat(kMasterPath, &s), SyscallSucceeds()); EXPECT_EQ(s.st_rdev, makedev(TTYAUX_MAJOR, kPtmxMinor)); EXPECT_EQ(s.st_size, 0); @@ -454,41 +454,41 @@ void ExpectReadable(const FileDescriptor& fd, int expected, char* buf) { EXPECT_EQ(expected, n); } -TEST(BasicPtyTest, OpenMainReplica) { - FileDescriptor main = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main)); +TEST(BasicPtyTest, OpenMasterReplica) { + FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); + FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master)); } -// The replica entry in /dev/pts/ disappears when the main is closed, even if +// The replica entry in /dev/pts/ disappears when the master is closed, even if // the replica is still open. -TEST(BasicPtyTest, ReplicaEntryGoneAfterMainClose) { - FileDescriptor main = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main)); +TEST(BasicPtyTest, ReplicaEntryGoneAfterMasterClose) { + FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); + FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master)); // Get pty index. int index = -1; - ASSERT_THAT(ioctl(main.get(), TIOCGPTN, &index), SyscallSucceeds()); + ASSERT_THAT(ioctl(master.get(), TIOCGPTN, &index), SyscallSucceeds()); std::string path = absl::StrCat("/dev/pts/", index); struct stat st; EXPECT_THAT(stat(path.c_str(), &st), SyscallSucceeds()); - main.reset(); + master.reset(); EXPECT_THAT(stat(path.c_str(), &st), SyscallFailsWithErrno(ENOENT)); } TEST(BasicPtyTest, Getdents) { - FileDescriptor main1 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); + FileDescriptor master1 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); int index1 = -1; - ASSERT_THAT(ioctl(main1.get(), TIOCGPTN, &index1), SyscallSucceeds()); - FileDescriptor replica1 = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main1)); + ASSERT_THAT(ioctl(master1.get(), TIOCGPTN, &index1), SyscallSucceeds()); + FileDescriptor replica1 = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master1)); - FileDescriptor main2 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); + FileDescriptor master2 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); int index2 = -1; - ASSERT_THAT(ioctl(main2.get(), TIOCGPTN, &index2), SyscallSucceeds()); - FileDescriptor replica2 = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main2)); + ASSERT_THAT(ioctl(master2.get(), TIOCGPTN, &index2), SyscallSucceeds()); + FileDescriptor replica2 = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master2)); // The directory contains ptmx, index1, and index2. (Plus any additional PTYs // unrelated to this test.) @@ -498,9 +498,9 @@ TEST(BasicPtyTest, Getdents) { EXPECT_THAT(contents, Contains(absl::StrCat(index1))); EXPECT_THAT(contents, Contains(absl::StrCat(index2))); - main2.reset(); + master2.reset(); - // The directory contains ptmx and index1, but not index2 since the main is + // The directory contains ptmx and index1, but not index2 since the master is // closed. (Plus any additional PTYs unrelated to this test.) contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev/pts/", true)); @@ -519,8 +519,8 @@ TEST(BasicPtyTest, Getdents) { class PtyTest : public ::testing::Test { protected: void SetUp() override { - main_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - replica_ = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main_)); + master_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); + replica_ = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master_)); } void DisableCanonical() { @@ -537,21 +537,22 @@ class PtyTest : public ::testing::Test { EXPECT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); } - // Main and replica ends of the PTY. Non-blocking. - FileDescriptor main_; + // Master and replica ends of the PTY. Non-blocking. + FileDescriptor master_; FileDescriptor replica_; }; -// Main to replica sanity test. -TEST_F(PtyTest, WriteMainToReplica) { - // N.B. by default, the replica reads nothing until the main writes a newline. +// Master to replica sanity test. +TEST_F(PtyTest, WriteMasterToReplica) { + // N.B. by default, the replica reads nothing until the master writes a + // newline. constexpr char kBuf[] = "hello\n"; - EXPECT_THAT(WriteFd(main_.get(), kBuf, sizeof(kBuf) - 1), + EXPECT_THAT(WriteFd(master_.get(), kBuf, sizeof(kBuf) - 1), SyscallSucceedsWithValue(sizeof(kBuf) - 1)); - // Linux moves data from the main to the replica via async work scheduled via - // tty_flip_buffer_push. Since it is asynchronous, the data may not be + // Linux moves data from the master to the replica via async work scheduled + // via tty_flip_buffer_push. Since it is asynchronous, the data may not be // available for reading immediately. Instead we must poll and assert that it // becomes available "soon". @@ -561,63 +562,63 @@ TEST_F(PtyTest, WriteMainToReplica) { EXPECT_EQ(memcmp(buf, kBuf, sizeof(kBuf)), 0); } -// Replica to main sanity test. -TEST_F(PtyTest, WriteReplicaToMain) { - // N.B. by default, the main reads nothing until the replica writes a newline, - // and the main gets a carriage return. +// Replica to master sanity test. +TEST_F(PtyTest, WriteReplicaToMaster) { + // N.B. by default, the master reads nothing until the replica writes a + // newline, and the master gets a carriage return. constexpr char kInput[] = "hello\n"; constexpr char kExpected[] = "hello\r\n"; EXPECT_THAT(WriteFd(replica_.get(), kInput, sizeof(kInput) - 1), SyscallSucceedsWithValue(sizeof(kInput) - 1)); - // Linux moves data from the main to the replica via async work scheduled via - // tty_flip_buffer_push. Since it is asynchronous, the data may not be + // Linux moves data from the master to the replica via async work scheduled + // via tty_flip_buffer_push. Since it is asynchronous, the data may not be // available for reading immediately. Instead we must poll and assert that it // becomes available "soon". char buf[sizeof(kExpected)] = {}; - ExpectReadable(main_, sizeof(buf) - 1, buf); + ExpectReadable(master_, sizeof(buf) - 1, buf); EXPECT_EQ(memcmp(buf, kExpected, sizeof(kExpected)), 0); } TEST_F(PtyTest, WriteInvalidUTF8) { char c = 0xff; - ASSERT_THAT(syscall(__NR_write, main_.get(), &c, sizeof(c)), + ASSERT_THAT(syscall(__NR_write, master_.get(), &c, sizeof(c)), SyscallSucceedsWithValue(sizeof(c))); } -// Both the main and replica report the standard default termios settings. +// Both the master and replica report the standard default termios settings. // -// Note that TCGETS on the main actually redirects to the replica (see comment -// on MainTermiosUnchangable). +// Note that TCGETS on the master actually redirects to the replica (see comment +// on MasterTermiosUnchangable). TEST_F(PtyTest, DefaultTermios) { struct kernel_termios t = {}; EXPECT_THAT(ioctl(replica_.get(), TCGETS, &t), SyscallSucceeds()); EXPECT_EQ(t, DefaultTermios()); - EXPECT_THAT(ioctl(main_.get(), TCGETS, &t), SyscallSucceeds()); + EXPECT_THAT(ioctl(master_.get(), TCGETS, &t), SyscallSucceeds()); EXPECT_EQ(t, DefaultTermios()); } -// Changing termios from the main actually affects the replica. +// Changing termios from the master actually affects the replica. // -// TCSETS on the main actually redirects to the replica (see comment on -// MainTermiosUnchangable). +// TCSETS on the master actually redirects to the replica (see comment on +// MasterTermiosUnchangable). TEST_F(PtyTest, TermiosAffectsReplica) { - struct kernel_termios main_termios = {}; - EXPECT_THAT(ioctl(main_.get(), TCGETS, &main_termios), SyscallSucceeds()); - main_termios.c_lflag ^= ICANON; - EXPECT_THAT(ioctl(main_.get(), TCSETS, &main_termios), SyscallSucceeds()); + struct kernel_termios master_termios = {}; + EXPECT_THAT(ioctl(master_.get(), TCGETS, &master_termios), SyscallSucceeds()); + master_termios.c_lflag ^= ICANON; + EXPECT_THAT(ioctl(master_.get(), TCSETS, &master_termios), SyscallSucceeds()); struct kernel_termios replica_termios = {}; EXPECT_THAT(ioctl(replica_.get(), TCGETS, &replica_termios), SyscallSucceeds()); - EXPECT_EQ(main_termios, replica_termios); + EXPECT_EQ(master_termios, replica_termios); } -// The main end of the pty has termios: +// The master end of the pty has termios: // // struct kernel_termios t = { // .c_iflag = 0; @@ -629,25 +630,25 @@ TEST_F(PtyTest, TermiosAffectsReplica) { // // (From drivers/tty/pty.c:unix98_pty_init) // -// All termios control ioctls on the main actually redirect to the replica +// All termios control ioctls on the master actually redirect to the replica // (drivers/tty/tty_ioctl.c:tty_mode_ioctl), making it impossible to change the -// main termios. +// master termios. // // Verify this by setting ICRNL (which rewrites input \r to \n) and verify that -// it has no effect on the main. -TEST_F(PtyTest, MainTermiosUnchangable) { - struct kernel_termios main_termios = {}; - EXPECT_THAT(ioctl(main_.get(), TCGETS, &main_termios), SyscallSucceeds()); - main_termios.c_lflag |= ICRNL; - EXPECT_THAT(ioctl(main_.get(), TCSETS, &main_termios), SyscallSucceeds()); +// it has no effect on the master. +TEST_F(PtyTest, MasterTermiosUnchangable) { + struct kernel_termios master_termios = {}; + EXPECT_THAT(ioctl(master_.get(), TCGETS, &master_termios), SyscallSucceeds()); + master_termios.c_lflag |= ICRNL; + EXPECT_THAT(ioctl(master_.get(), TCSETS, &master_termios), SyscallSucceeds()); char c = '\r'; ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - ExpectReadable(main_, 1, &c); + ExpectReadable(master_, 1, &c); EXPECT_EQ(c, '\r'); // ICRNL had no effect! - ExpectFinished(main_); + ExpectFinished(master_); } // ICRNL rewrites input \r to \n. @@ -658,7 +659,7 @@ TEST_F(PtyTest, TermiosICRNL) { ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); char c = '\r'; - ASSERT_THAT(WriteFd(main_.get(), &c, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1)); ExpectReadable(replica_, 1, &c); EXPECT_EQ(c, '\n'); @@ -678,7 +679,7 @@ TEST_F(PtyTest, TermiosONLCR) { // Extra byte for NUL for EXPECT_STREQ. char buf[3] = {}; - ExpectReadable(main_, 2, buf); + ExpectReadable(master_, 2, buf); EXPECT_STREQ(buf, "\r\n"); ExpectFinished(replica_); @@ -691,7 +692,7 @@ TEST_F(PtyTest, TermiosIGNCR) { ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); char c = '\r'; - ASSERT_THAT(WriteFd(main_.get(), &c, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1)); // Nothing to read. ASSERT_THAT(PollAndReadFd(replica_.get(), &c, 1, kTimeout), @@ -725,18 +726,18 @@ TEST_F(PtyTest, TermiosPollReplica) { absl::SleepFor(absl::Seconds(1)); char s[] = "foo\n"; - ASSERT_THAT(WriteFd(main_.get(), s, strlen(s) + 1), SyscallSucceeds()); + ASSERT_THAT(WriteFd(master_.get(), s, strlen(s) + 1), SyscallSucceeds()); } -// Test that we can successfully poll for readable data from the main. -TEST_F(PtyTest, TermiosPollMain) { +// Test that we can successfully poll for readable data from the master. +TEST_F(PtyTest, TermiosPollMaster) { struct kernel_termios t = DefaultTermios(); t.c_iflag |= IGNCR; t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(main_.get(), TCSETS, &t), SyscallSucceeds()); + ASSERT_THAT(ioctl(master_.get(), TCSETS, &t), SyscallSucceeds()); absl::Notification notify; - int mfd = main_.get(); + int mfd = master_.get(); ScopedThread th([mfd, ¬ify]() { notify.Notify(); @@ -765,7 +766,7 @@ TEST_F(PtyTest, TermiosINLCR) { ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); char c = '\n'; - ASSERT_THAT(WriteFd(main_.get(), &c, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1)); ExpectReadable(replica_, 1, &c); EXPECT_EQ(c, '\r'); @@ -784,7 +785,7 @@ TEST_F(PtyTest, TermiosONOCR) { ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); // Nothing to read. - ASSERT_THAT(PollAndReadFd(main_.get(), &c, 1, kTimeout), + ASSERT_THAT(PollAndReadFd(master_.get(), &c, 1, kTimeout), PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); // This time the column is greater than 0, so we should be able to read the CR @@ -795,17 +796,17 @@ TEST_F(PtyTest, TermiosONOCR) { SyscallSucceedsWithValue(kInputSize)); char buf[kInputSize] = {}; - ExpectReadable(main_, kInputSize, buf); + ExpectReadable(master_, kInputSize, buf); EXPECT_EQ(memcmp(buf, kInput, kInputSize), 0); - ExpectFinished(main_); + ExpectFinished(master_); // Terminal should be at column 0 again, so no CR can be read. ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); // Nothing to read. - ASSERT_THAT(PollAndReadFd(main_.get(), &c, 1, kTimeout), + ASSERT_THAT(PollAndReadFd(master_.get(), &c, 1, kTimeout), PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); } @@ -819,10 +820,10 @@ TEST_F(PtyTest, TermiosOCRNL) { char c = '\r'; ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - ExpectReadable(main_, 1, &c); + ExpectReadable(master_, 1, &c); EXPECT_EQ(c, '\n'); - ExpectFinished(main_); + ExpectFinished(master_); } // Tests that VEOL is disabled when we start, and that we can set it to enable @@ -830,7 +831,7 @@ TEST_F(PtyTest, TermiosOCRNL) { TEST_F(PtyTest, VEOLTermination) { // Write a few bytes ending with '\0', and confirm that we can't read. constexpr char kInput[] = "hello"; - ASSERT_THAT(WriteFd(main_.get(), kInput, sizeof(kInput)), + ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)), SyscallSucceedsWithValue(sizeof(kInput))); char buf[sizeof(kInput)] = {}; ASSERT_THAT(PollAndReadFd(replica_.get(), buf, sizeof(kInput), kTimeout), @@ -841,7 +842,7 @@ TEST_F(PtyTest, VEOLTermination) { struct kernel_termios t = DefaultTermios(); t.c_cc[VEOL] = delim; ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - ASSERT_THAT(WriteFd(main_.get(), &delim, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); // Now we can read, as sending EOL caused the line to become available. ExpectReadable(replica_, sizeof(kInput), buf); @@ -861,7 +862,7 @@ TEST_F(PtyTest, CanonBigWrite) { char input[kWriteLen]; memset(input, 'M', kWriteLen - 1); input[kWriteLen - 1] = '\n'; - ASSERT_THAT(WriteFd(main_.get(), input, kWriteLen), + ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen), SyscallSucceedsWithValue(kWriteLen)); // We can read the line. @@ -877,7 +878,7 @@ TEST_F(PtyTest, SwitchCanonToNoncanon) { // Write a few bytes without a terminating character, switch to noncanonical // mode, and read them. constexpr char kInput[] = "hello"; - ASSERT_THAT(WriteFd(main_.get(), kInput, sizeof(kInput)), + ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)), SyscallSucceedsWithValue(sizeof(kInput))); // Nothing available yet. @@ -896,7 +897,7 @@ TEST_F(PtyTest, SwitchCanonToNoncanon) { TEST_F(PtyTest, SwitchCanonToNonCanonNewline) { // Write a few bytes with a terminating character. constexpr char kInput[] = "hello\n"; - ASSERT_THAT(WriteFd(main_.get(), kInput, sizeof(kInput)), + ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)), SyscallSucceedsWithValue(sizeof(kInput))); DisableCanonical(); @@ -916,12 +917,12 @@ TEST_F(PtyTest, SwitchNoncanonToCanonNewlineBig) { constexpr int kWriteLen = 4100; char input[kWriteLen]; memset(input, 'M', kWriteLen); - ASSERT_THAT(WriteFd(main_.get(), input, kWriteLen), + ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen), SyscallSucceedsWithValue(kWriteLen)); // Wait for the input queue to fill. ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kMaxLineSize - 1)); constexpr char delim = '\n'; - ASSERT_THAT(WriteFd(main_.get(), &delim, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); EnableCanonical(); @@ -941,7 +942,7 @@ TEST_F(PtyTest, SwitchNoncanonToCanonNoNewline) { // Write a few bytes without a terminating character. // mode, and read them. constexpr char kInput[] = "hello"; - ASSERT_THAT(WriteFd(main_.get(), kInput, sizeof(kInput) - 1), + ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput) - 1), SyscallSucceedsWithValue(sizeof(kInput) - 1)); ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), sizeof(kInput) - 1)); @@ -963,7 +964,7 @@ TEST_F(PtyTest, SwitchNoncanonToCanonNoNewlineBig) { constexpr int kWriteLen = 4100; char input[kWriteLen]; memset(input, 'M', kWriteLen); - ASSERT_THAT(WriteFd(main_.get(), input, kWriteLen), + ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen), SyscallSucceedsWithValue(kWriteLen)); ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kMaxLineSize - 1)); @@ -987,12 +988,12 @@ TEST_F(PtyTest, NoncanonBigWrite) { for (int i = 0; i < kInputSize; i++) { // This makes too many syscalls for save/restore. const DisableSave ds; - ASSERT_THAT(WriteFd(main_.get(), &kInput, sizeof(kInput)), + ASSERT_THAT(WriteFd(master_.get(), &kInput, sizeof(kInput)), SyscallSucceedsWithValue(sizeof(kInput))); } // We should be able to read out everything. Sleep a bit so that Linux has a - // chance to move data from the main to the replica. + // chance to move data from the master to the replica. ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kMaxLineSize - 1)); for (int i = 0; i < kInputSize; i++) { // This makes too many syscalls for save/restore. @@ -1010,7 +1011,7 @@ TEST_F(PtyTest, NoncanonBigWrite) { // Test newline. TEST_F(PtyTest, TermiosICANONNewline) { char input[3] = {'a', 'b', 'c'}; - ASSERT_THAT(WriteFd(main_.get(), input, sizeof(input)), + ASSERT_THAT(WriteFd(master_.get(), input, sizeof(input)), SyscallSucceedsWithValue(sizeof(input))); // Extra bytes for newline (written later) and NUL for EXPECT_STREQ. @@ -1021,7 +1022,7 @@ TEST_F(PtyTest, TermiosICANONNewline) { PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); char delim = '\n'; - ASSERT_THAT(WriteFd(main_.get(), &delim, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); // Now it is available. ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), sizeof(input) + 1)); @@ -1036,7 +1037,7 @@ TEST_F(PtyTest, TermiosICANONNewline) { // Test EOF (^D). TEST_F(PtyTest, TermiosICANONEOF) { char input[3] = {'a', 'b', 'c'}; - ASSERT_THAT(WriteFd(main_.get(), input, sizeof(input)), + ASSERT_THAT(WriteFd(master_.get(), input, sizeof(input)), SyscallSucceedsWithValue(sizeof(input))); // Extra byte for NUL for EXPECT_STREQ. @@ -1046,7 +1047,7 @@ TEST_F(PtyTest, TermiosICANONEOF) { ASSERT_THAT(PollAndReadFd(replica_.get(), buf, sizeof(input), kTimeout), PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); char delim = ControlCharacter('D'); - ASSERT_THAT(WriteFd(main_.get(), &delim, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); // Now it is available. Note that ^D is not included. ExpectReadable(replica_, sizeof(input), buf); @@ -1069,10 +1070,10 @@ TEST_F(PtyTest, CanonDiscard) { // This makes too many syscalls for save/restore. const DisableSave ds; for (int i = 0; i < kInputSize; i++) { - ASSERT_THAT(WriteFd(main_.get(), &kInput, sizeof(kInput)), + ASSERT_THAT(WriteFd(master_.get(), &kInput, sizeof(kInput)), SyscallSucceedsWithValue(sizeof(kInput))); } - ASSERT_THAT(WriteFd(main_.get(), &delim, 1), SyscallSucceedsWithValue(1)); + ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); } // There should be multiple truncated lines available to read. @@ -1091,9 +1092,9 @@ TEST_F(PtyTest, CanonMultiline) { constexpr char kInput2[] = "BLUE\n"; // Write both lines. - ASSERT_THAT(WriteFd(main_.get(), kInput1, sizeof(kInput1) - 1), + ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1), SyscallSucceedsWithValue(sizeof(kInput1) - 1)); - ASSERT_THAT(WriteFd(main_.get(), kInput2, sizeof(kInput2) - 1), + ASSERT_THAT(WriteFd(master_.get(), kInput2, sizeof(kInput2) - 1), SyscallSucceedsWithValue(sizeof(kInput2) - 1)); // Get the first line. @@ -1117,9 +1118,9 @@ TEST_F(PtyTest, SwitchNoncanonToCanonMultiline) { constexpr char kExpected[] = "GO\nBLUE\n"; // Write both lines. - ASSERT_THAT(WriteFd(main_.get(), kInput1, sizeof(kInput1) - 1), + ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1), SyscallSucceedsWithValue(sizeof(kInput1) - 1)); - ASSERT_THAT(WriteFd(main_.get(), kInput2, sizeof(kInput2) - 1), + ASSERT_THAT(WriteFd(master_.get(), kInput2, sizeof(kInput2) - 1), SyscallSucceedsWithValue(sizeof(kInput2) - 1)); ASSERT_NO_ERRNO( @@ -1140,7 +1141,7 @@ TEST_F(PtyTest, SwitchTwiceMultiline) { // Write each line. for (const std::string& input : kInputs) { - ASSERT_THAT(WriteFd(main_.get(), input.c_str(), input.size()), + ASSERT_THAT(WriteFd(master_.get(), input.c_str(), input.size()), SyscallSucceedsWithValue(input.size())); } @@ -1162,7 +1163,7 @@ TEST_F(PtyTest, SwitchTwiceMultiline) { TEST_F(PtyTest, QueueSize) { // Write the line. constexpr char kInput1[] = "GO\n"; - ASSERT_THAT(WriteFd(main_.get(), kInput1, sizeof(kInput1) - 1), + ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1), SyscallSucceedsWithValue(sizeof(kInput1) - 1)); ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), sizeof(kInput1) - 1)); @@ -1170,7 +1171,7 @@ TEST_F(PtyTest, QueueSize) { // readable size. char input[kMaxLineSize]; memset(input, 'M', kMaxLineSize); - ASSERT_THAT(WriteFd(main_.get(), input, kMaxLineSize), + ASSERT_THAT(WriteFd(master_.get(), input, kMaxLineSize), SyscallSucceedsWithValue(kMaxLineSize)); int inputBufSize = ASSERT_NO_ERRNO_AND_VALUE( WaitUntilReceived(replica_.get(), sizeof(kInput1) - 1)); @@ -1192,10 +1193,11 @@ TEST_F(PtyTest, PartialBadBuffer) { // Leave only one free byte in the buffer. char* bad_buffer = buf + kPageSize - 1; - // Write to the main. + // Write to the master. constexpr char kBuf[] = "hello\n"; constexpr size_t size = sizeof(kBuf) - 1; - EXPECT_THAT(WriteFd(main_.get(), kBuf, size), SyscallSucceedsWithValue(size)); + EXPECT_THAT(WriteFd(master_.get(), kBuf, size), + SyscallSucceedsWithValue(size)); // Read from the replica into bad_buffer. ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), size)); @@ -1207,14 +1209,14 @@ TEST_F(PtyTest, PartialBadBuffer) { TEST_F(PtyTest, SimpleEcho) { constexpr char kInput[] = "Mr. Eko"; - EXPECT_THAT(WriteFd(main_.get(), kInput, strlen(kInput)), + EXPECT_THAT(WriteFd(master_.get(), kInput, strlen(kInput)), SyscallSucceedsWithValue(strlen(kInput))); char buf[100] = {}; - ExpectReadable(main_, strlen(kInput), buf); + ExpectReadable(master_, strlen(kInput), buf); EXPECT_STREQ(buf, kInput); - ExpectFinished(main_); + ExpectFinished(master_); } TEST_F(PtyTest, GetWindowSize) { @@ -1231,16 +1233,17 @@ TEST_F(PtyTest, SetReplicaWindowSize) { ASSERT_THAT(ioctl(replica_.get(), TIOCSWINSZ, &ws), SyscallSucceeds()); struct winsize retrieved_ws = {}; - ASSERT_THAT(ioctl(main_.get(), TIOCGWINSZ, &retrieved_ws), SyscallSucceeds()); + ASSERT_THAT(ioctl(master_.get(), TIOCGWINSZ, &retrieved_ws), + SyscallSucceeds()); EXPECT_EQ(retrieved_ws.ws_row, kRows); EXPECT_EQ(retrieved_ws.ws_col, kCols); } -TEST_F(PtyTest, SetMainWindowSize) { +TEST_F(PtyTest, SetMasterWindowSize) { constexpr uint16_t kRows = 343; constexpr uint16_t kCols = 2401; struct winsize ws = {.ws_row = kRows, .ws_col = kCols}; - ASSERT_THAT(ioctl(main_.get(), TIOCSWINSZ, &ws), SyscallSucceeds()); + ASSERT_THAT(ioctl(master_.get(), TIOCSWINSZ, &ws), SyscallSucceeds()); struct winsize retrieved_ws = {}; ASSERT_THAT(ioctl(replica_.get(), TIOCGWINSZ, &retrieved_ws), @@ -1252,8 +1255,8 @@ TEST_F(PtyTest, SetMainWindowSize) { class JobControlTest : public ::testing::Test { protected: void SetUp() override { - main_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - replica_ = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main_)); + master_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); + replica_ = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master_)); // Make this a session leader, which also drops the controlling terminal. // In the gVisor test environment, this test will be run as the session @@ -1277,15 +1280,15 @@ class JobControlTest : public ::testing::Test { return PosixError(wstatus, "process returned"); } - // Main and replica ends of the PTY. Non-blocking. - FileDescriptor main_; + // Master and replica ends of the PTY. Non-blocking. + FileDescriptor master_; FileDescriptor replica_; }; -TEST_F(JobControlTest, SetTTYMain) { +TEST_F(JobControlTest, SetTTYMaster) { auto res = RunInChild([=]() { TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(main_.get(), TIOCSCTTY, 0)); + TEST_PCHECK(!ioctl(master_.get(), TIOCSCTTY, 0)); }); ASSERT_NO_ERRNO(res); } @@ -1360,7 +1363,7 @@ TEST_F(JobControlTest, ReleaseWrongTTY) { auto res = RunInChild([=]() { TEST_PCHECK(setsid() >= 0); TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - TEST_PCHECK(ioctl(main_.get(), TIOCNOTTY) < 0 && errno == ENOTTY); + TEST_PCHECK(ioctl(master_.get(), TIOCNOTTY) < 0 && errno == ENOTTY); }); ASSERT_NO_ERRNO(res); } diff --git a/test/syscalls/linux/pty_root.cc b/test/syscalls/linux/pty_root.cc index a534cf0bb..4ac648729 100644 --- a/test/syscalls/linux/pty_root.cc +++ b/test/syscalls/linux/pty_root.cc @@ -48,9 +48,9 @@ TEST(JobControlRootTest, StealTTY) { ASSERT_THAT(setsid(), SyscallSucceeds()); } - FileDescriptor main = + FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(main)); + FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master)); // Make replica the controlling terminal. ASSERT_THAT(ioctl(replica.get(), TIOCSCTTY, 0), SyscallSucceeds()); diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc index 3de898df7..1b9dbc584 100644 --- a/test/syscalls/linux/raw_socket_icmp.cc +++ b/test/syscalls/linux/raw_socket_icmp.cc @@ -416,6 +416,28 @@ TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) { ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); } +// Set and get SO_LINGER. +TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int level = SOL_SOCKET; + int type = SO_LINGER; + + struct linger sl; + sl.l_onoff = 1; + sl.l_linger = 5; + ASSERT_THAT(setsockopt(s_, level, type, &sl, sizeof(sl)), + SyscallSucceedsWithValue(0)); + + struct linger got_linger = {}; + socklen_t length = sizeof(sl); + ASSERT_THAT(getsockopt(s_, level, type, &got_linger, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got_linger)); + EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); +} + 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/rename.cc b/test/syscalls/linux/rename.cc index 833c0dc4f..5458f54ad 100644 --- a/test/syscalls/linux/rename.cc +++ b/test/syscalls/linux/rename.cc @@ -170,6 +170,9 @@ TEST(RenameTest, FileOverwritesFile) { } TEST(RenameTest, DirectoryOverwritesDirectoryLinkCount) { + // Directory link counts are synthetic on overlay filesystems. + SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))); + auto parent1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(2)); diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index 425084228..11fcec443 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -1158,33 +1158,37 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { SyscallSucceeds()); ASSERT_THAT(close(conn_fd.release()), SyscallSucceeds()); - // TODO(gvisor.dev/issue/3780): Remove this. if (IsRunningOnGvisor()) { - // Wait for the RST to be observed. + // Gvisor packet procssing is asynchronous and can take a bit of time in + // some cases so we give it a bit of time to process the RST packet before + // calling accept. + // + // There is nothing to poll() on so we have no choice but to use a sleep + // here. absl::SleepFor(absl::Milliseconds(100)); } sockaddr_storage accept_addr; socklen_t addrlen = sizeof(accept_addr); - // TODO(gvisor.dev/issue/3780): Remove this. - if (IsRunningOnGvisor()) { - ASSERT_THAT(accept(listen_fd.get(), - reinterpret_cast<sockaddr*>(&accept_addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); - return; - } - - conn_fd = ASSERT_NO_ERRNO_AND_VALUE(Accept( + auto accept_fd = ASSERT_NO_ERRNO_AND_VALUE(Accept( listen_fd.get(), reinterpret_cast<sockaddr*>(&accept_addr), &addrlen)); ASSERT_EQ(addrlen, listener.addr_len); - int err; - socklen_t optlen = sizeof(err); - ASSERT_THAT(getsockopt(conn_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), - SyscallSucceeds()); - ASSERT_EQ(err, ECONNRESET); - ASSERT_EQ(optlen, sizeof(err)); + // TODO(gvisor.dev/issue/3812): Remove after SO_ERROR is fixed. + if (IsRunningOnGvisor()) { + char buf[10]; + ASSERT_THAT(ReadFd(accept_fd.get(), buf, sizeof(buf)), + SyscallFailsWithErrno(ECONNRESET)); + } else { + int err; + socklen_t optlen = sizeof(err); + ASSERT_THAT( + getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + ASSERT_EQ(err, ECONNRESET); + ASSERT_EQ(optlen, sizeof(err)); + } } // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc index 6e4ecd680..3f2c0fdf2 100644 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ b/test/syscalls/linux/socket_ip_udp_generic.cc @@ -451,7 +451,7 @@ TEST_P(UDPSocketPairTest, TClassRecvMismatch) { } // Test the SO_LINGER option can be set/get on udp socket. -TEST_P(UDPSocketPairTest, SoLingerFail) { +TEST_P(UDPSocketPairTest, SetAndGetSocketLinger) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); int level = SOL_SOCKET; int type = SO_LINGER; @@ -469,15 +469,7 @@ TEST_P(UDPSocketPairTest, SoLingerFail) { SyscallSucceedsWithValue(0)); ASSERT_EQ(length, sizeof(got_linger)); - // Linux returns the values which are set in the SetSockOpt for SO_LINGER. - // In gVisor, we do not store the linger values for UDP as SO_LINGER for UDP - // is a no-op. - if (IsRunningOnGvisor()) { - struct linger want_linger = {}; - EXPECT_EQ(0, memcmp(&want_linger, &got_linger, length)); - } else { - EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); - } + EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); } } // namespace testing diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index 02ea05e22..a72c76c97 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -27,6 +27,7 @@ #include "absl/memory/memory.h" #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/test_util.h" namespace gvisor { @@ -73,9 +74,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNoGroup) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + EXPECT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } // Check that not setting a default send interface prevents multicast packets @@ -207,8 +208,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackAddr) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -262,8 +264,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -317,8 +320,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddr) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -372,8 +376,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -431,8 +436,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrConnect) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -490,8 +496,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -545,8 +552,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelf) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket1->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -600,8 +608,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket1->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -659,9 +668,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfConnect) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT(RetryEINTR(recv)(socket1->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + EXPECT_THAT( + RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } // Check that multicast works when the default send interface is configured by @@ -717,9 +726,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT(RetryEINTR(recv)(socket1->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + EXPECT_THAT( + RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } // Check that multicast works when the default send interface is configured by @@ -775,8 +784,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfNoLoop) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket1->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -834,8 +844,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket1->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -907,9 +918,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropAddr) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + EXPECT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } // Check that dropping a group membership prevents multicast packets from being @@ -965,9 +976,9 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) { // Check that we did not receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + EXPECT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfZero) { @@ -1319,9 +1330,9 @@ 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( - RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT(RecvMsgTimeout(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))); } } @@ -1398,9 +1409,9 @@ 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( - RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT(RecvMsgTimeout(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))); } } @@ -1421,9 +1432,9 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) { char recv_buf[sizeof(send_buf)] = {}; for (auto& sockets : socket_pairs) { - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, - sizeof(recv_buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf, + sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } } } @@ -1474,9 +1485,9 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenJoinThenReceive) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1518,9 +1529,9 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenNoJoinThenNoReceive) { // Check that we don't receive the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } // Check that a socket can bind to a multicast address and still send out @@ -1568,9 +1579,9 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenSend) { // Check that we received the packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1615,9 +1626,9 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenReceive) { // Check that we received the multicast packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1666,9 +1677,9 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenSend) { // Check that we received the packet. char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_THAT( + RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(recv_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } @@ -1726,17 +1737,17 @@ 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( - RetryEINTR(recv)(last->get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT), - SyscallSucceedsWithValue(sizeof(send_buf))); + RecvMsgTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(send_buf))); EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } // Verify that no other messages were received. for (auto& socket : sockets) { char recv_buf[kMessageSize] = {}; - EXPECT_THAT(RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); + EXPECT_THAT(RecvMsgTimeout(socket->get(), recv_buf, sizeof(recv_buf), + 1 /*timeout*/), + PosixErrorIs(EAGAIN, ::testing::_)); } } @@ -2113,12 +2124,12 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) { // balancing (REUSEPORT) instead of the most recently bound socket // (REUSEADDR). char recv_buf[kMessageSize] = {}; - EXPECT_THAT(RetryEINTR(recv)(receiver1->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallSucceedsWithValue(kMessageSize)); - EXPECT_THAT(RetryEINTR(recv)(receiver2->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallSucceedsWithValue(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)); } // Test that socket will receive packet info control message. diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc index 79eb48afa..49a0f06d9 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc @@ -15,10 +15,12 @@ #include "test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h" #include <arpa/inet.h> +#include <poll.h> #include "gtest/gtest.h" #include "test/syscalls/linux/socket_netlink_route_util.h" #include "test/util/capability_util.h" +#include "test/util/cleanup.h" namespace gvisor { namespace testing { @@ -33,9 +35,23 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { // Add an IP address to the loopback interface. Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); struct in_addr addr; - EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr)); - EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, + ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr)); + 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)] { + 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))); + } + }); auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto rcv_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -45,10 +61,10 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { TestAddress sender_addr("V4NotAssignd1"); sender_addr.addr.ss_family = AF_INET; sender_addr.addr_len = sizeof(sockaddr_in); - EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.2", + ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.2", &(reinterpret_cast<sockaddr_in*>(&sender_addr.addr) ->sin_addr.s_addr))); - EXPECT_THAT( + ASSERT_THAT( bind(snd_sock->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), sender_addr.addr_len), SyscallSucceeds()); @@ -58,10 +74,10 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { TestAddress receiver_addr("V4NotAssigned2"); receiver_addr.addr.ss_family = AF_INET; receiver_addr.addr_len = sizeof(sockaddr_in); - EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.254", + ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.254", &(reinterpret_cast<sockaddr_in*>(&receiver_addr.addr) ->sin_addr.s_addr))); - EXPECT_THAT( + ASSERT_THAT( bind(rcv_sock->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), receiver_addr.addr_len), SyscallSucceeds()); @@ -70,10 +86,10 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { reinterpret_cast<sockaddr*>(&receiver_addr.addr), &receiver_addr_len), SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + ASSERT_EQ(receiver_addr_len, receiver_addr.addr_len); char send_buf[kSendBufSize]; RandomizeBuffer(send_buf, kSendBufSize); - EXPECT_THAT( + ASSERT_THAT( RetryEINTR(sendto)(snd_sock->get(), send_buf, kSendBufSize, 0, reinterpret_cast<sockaddr*>(&receiver_addr.addr), receiver_addr.addr_len), @@ -83,7 +99,126 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { char recv_buf[kSendBufSize] = {}; ASSERT_THAT(RetryEINTR(recv)(rcv_sock->get(), recv_buf, kSendBufSize, 0), SyscallSucceedsWithValue(kSendBufSize)); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize)); + ASSERT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize)); +} + +// Tests that broadcast packets are delivered to all interested sockets +// (wildcard and broadcast address specified sockets). +// +// Note, we cannot test the IPv4 Broadcast (255.255.255.255) because we do +// not have a route to it. +TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { + constexpr uint16_t kPort = 9876; + // Wait up to 20 seconds for the data. + constexpr int kPollTimeoutMs = 20000; + // Number of sockets per socket type. + constexpr int kNumSocketsPerType = 2; + + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + + // Add an IP address to the loopback interface. + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + struct in_addr addr; + ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr)); + ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, + 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))); + } + }); + + TestAddress broadcast_address("SubnetBroadcastAddress"); + broadcast_address.addr.ss_family = AF_INET; + broadcast_address.addr_len = sizeof(sockaddr_in); + auto broadcast_address_in = + reinterpret_cast<sockaddr_in*>(&broadcast_address.addr); + ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.255", + &broadcast_address_in->sin_addr.s_addr)); + broadcast_address_in->sin_port = htons(kPort); + + TestAddress any_address = V4Any(); + reinterpret_cast<sockaddr_in*>(&any_address.addr)->sin_port = htons(kPort); + + // We create sockets bound to both the wildcard address and the broadcast + // address to make sure both of these types of "broadcast interested" sockets + // receive broadcast packets. + std::vector<std::unique_ptr<FileDescriptor>> socks; + for (bool bind_wildcard : {false, true}) { + // Create multiple sockets for each type of "broadcast interested" + // socket so we can test that all sockets receive the broadcast packet. + for (int i = 0; i < kNumSocketsPerType; i++) { + auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto idx = socks.size(); + + ASSERT_THAT(setsockopt(sock->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceedsWithValue(0)) + << "socks[" << idx << "]"; + + ASSERT_THAT(setsockopt(sock->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceedsWithValue(0)) + << "socks[" << idx << "]"; + + if (bind_wildcard) { + ASSERT_THAT( + bind(sock->get(), reinterpret_cast<sockaddr*>(&any_address.addr), + any_address.addr_len), + SyscallSucceeds()) + << "socks[" << idx << "]"; + } else { + ASSERT_THAT(bind(sock->get(), + reinterpret_cast<sockaddr*>(&broadcast_address.addr), + broadcast_address.addr_len), + SyscallSucceeds()) + << "socks[" << idx << "]"; + } + + socks.push_back(std::move(sock)); + } + } + + char send_buf[kSendBufSize]; + RandomizeBuffer(send_buf, kSendBufSize); + + // Broadcasts from each socket should be received by every socket (including + // the sending socket). + for (int w = 0; w < socks.size(); w++) { + auto& w_sock = socks[w]; + ASSERT_THAT( + RetryEINTR(sendto)(w_sock->get(), send_buf, kSendBufSize, 0, + reinterpret_cast<sockaddr*>(&broadcast_address.addr), + broadcast_address.addr_len), + SyscallSucceedsWithValue(kSendBufSize)) + << "write socks[" << w << "]"; + + // Check that we received the packet on all sockets. + for (int r = 0; r < socks.size(); r++) { + auto& r_sock = socks[r]; + + struct pollfd poll_fd = {r_sock->get(), POLLIN, 0}; + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), + SyscallSucceedsWithValue(1)) + << "write socks[" << w << "] & read socks[" << r << "]"; + + char recv_buf[kSendBufSize] = {}; + EXPECT_THAT(RetryEINTR(recv)(r_sock->get(), recv_buf, kSendBufSize, 0), + SyscallSucceedsWithValue(kSendBufSize)) + << "write socks[" << w << "] & read socks[" << r << "]"; + EXPECT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize)) + << "write socks[" << w << "] & read socks[" << r << "]"; + } + } } } // namespace testing diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc index bde1dbb4d..7a0bad4cb 100644 --- a/test/syscalls/linux/socket_netlink_route_util.cc +++ b/test/syscalls/linux/socket_netlink_route_util.cc @@ -26,6 +26,62 @@ namespace { constexpr uint32_t kSeq = 12345; +// Types of address modifications that may be performed on an interface. +enum class LinkAddrModification { + kAdd, + kDelete, +}; + +// Populates |hdr| with appripriate values for the modification type. +PosixError PopulateNlmsghdr(LinkAddrModification modification, + struct nlmsghdr* hdr) { + switch (modification) { + case LinkAddrModification::kAdd: + hdr->nlmsg_type = RTM_NEWADDR; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + return NoError(); + case LinkAddrModification::kDelete: + hdr->nlmsg_type = RTM_DELADDR; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + return NoError(); + } + + return PosixError(EINVAL); +} + +// Adds or removes the specified address from the specified interface. +PosixError LinkModifyLocalAddr(int index, int family, int prefixlen, + const void* addr, int addrlen, + LinkAddrModification modification) { + ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); + + struct request { + struct nlmsghdr hdr; + struct ifaddrmsg ifaddr; + char attrbuf[512]; + }; + + struct request req = {}; + PosixError err = PopulateNlmsghdr(modification, &req.hdr); + if (!err.ok()) { + return err; + } + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifaddr)); + req.hdr.nlmsg_seq = kSeq; + req.ifaddr.ifa_index = index; + req.ifaddr.ifa_family = family; + req.ifaddr.ifa_prefixlen = prefixlen; + + struct rtattr* rta = reinterpret_cast<struct rtattr*>( + reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len)); + rta->rta_type = IFA_LOCAL; + rta->rta_len = RTA_LENGTH(addrlen); + req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen); + memcpy(RTA_DATA(rta), addr, addrlen); + + return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); +} + } // namespace PosixError DumpLinks( @@ -84,31 +140,14 @@ PosixErrorOr<Link> LoopbackLink() { PosixError LinkAddLocalAddr(int index, int family, int prefixlen, const void* addr, int addrlen) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifaddrmsg ifaddr; - char attrbuf[512]; - }; - - struct request req = {}; - req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifaddr)); - req.hdr.nlmsg_type = RTM_NEWADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - req.hdr.nlmsg_seq = kSeq; - req.ifaddr.ifa_index = index; - req.ifaddr.ifa_family = family; - req.ifaddr.ifa_prefixlen = prefixlen; - - struct rtattr* rta = reinterpret_cast<struct rtattr*>( - reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len)); - rta->rta_type = IFA_LOCAL; - rta->rta_len = RTA_LENGTH(addrlen); - req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen); - memcpy(RTA_DATA(rta), addr, addrlen); + return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, + LinkAddrModification::kAdd); +} - return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); +PosixError LinkDelLocalAddr(int index, int family, int prefixlen, + const void* addr, int addrlen) { + return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, + LinkAddrModification::kDelete); } PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change) { diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h index 149c4a7f6..e5badca70 100644 --- a/test/syscalls/linux/socket_netlink_route_util.h +++ b/test/syscalls/linux/socket_netlink_route_util.h @@ -43,6 +43,10 @@ PosixErrorOr<Link> LoopbackLink(); PosixError LinkAddLocalAddr(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); + // LinkChangeFlags changes interface flags. E.g. IFF_UP. PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change); diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index 53b678e94..e11792309 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -753,6 +753,20 @@ 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) { + 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(recv)(sock, buf, buf_size, 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 734b48b96..468bc96e0 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -467,6 +467,10 @@ 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); + // 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 99e77b89e..1edcb15a7 100644 --- a/test/syscalls/linux/socket_unix_stream.cc +++ b/test/syscalls/linux/socket_unix_stream.cc @@ -103,6 +103,24 @@ TEST_P(StreamUnixSocketPairTest, Sendto) { SyscallFailsWithErrno(EISCONN)); } +TEST_P(StreamUnixSocketPairTest, SetAndGetSocketLinger) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + struct linger sl = {1, 5}; + EXPECT_THAT( + setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), + SyscallSucceedsWithValue(0)); + + struct linger got_linger = {}; + socklen_t length = sizeof(sl); + EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, + &got_linger, &length), + SyscallSucceedsWithValue(0)); + + ASSERT_EQ(length, sizeof(got_linger)); + EXPECT_EQ(0, memcmp(&got_linger, &sl, length)); +} + 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 2503960f3..92260b1e1 100644 --- a/test/syscalls/linux/stat.cc +++ b/test/syscalls/linux/stat.cc @@ -97,6 +97,11 @@ TEST_F(StatTest, FstatatSymlink) { } TEST_F(StatTest, Nlinks) { + // Skip this test if we are testing overlayfs because overlayfs does not + // (intentionally) return the correct nlink value for directories. + // See fs/overlayfs/inode.c:ovl_getattr(). + SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))); + TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Directory is initially empty, it should contain 2 links (one from itself, @@ -328,20 +333,23 @@ TEST_F(StatTest, LeadingDoubleSlash) { // Test that a rename doesn't change the underlying file. TEST_F(StatTest, StatDoesntChangeAfterRename) { - const TempPath old_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const TempPath old_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const TempPath new_path(NewTempAbsPath()); struct stat st_old = {}; struct stat st_new = {}; - ASSERT_THAT(stat(old_dir.path().c_str(), &st_old), SyscallSucceeds()); - ASSERT_THAT(rename(old_dir.path().c_str(), new_path.path().c_str()), + ASSERT_THAT(stat(old_file.path().c_str(), &st_old), SyscallSucceeds()); + ASSERT_THAT(rename(old_file.path().c_str(), new_path.path().c_str()), SyscallSucceeds()); ASSERT_THAT(stat(new_path.path().c_str(), &st_new), SyscallSucceeds()); EXPECT_EQ(st_old.st_nlink, st_new.st_nlink); EXPECT_EQ(st_old.st_dev, st_new.st_dev); - EXPECT_EQ(st_old.st_ino, st_new.st_ino); + // Overlay filesystems may synthesize directory inode numbers on the fly. + if (!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); EXPECT_EQ(st_old.st_uid, st_new.st_uid); EXPECT_EQ(st_old.st_gid, st_new.st_gid); @@ -378,7 +386,9 @@ TEST_F(StatTest, LinkCountsWithRegularFileChild) { // This test verifies that inodes remain around when there is an open fd // after link count hits 0. -TEST_F(StatTest, ZeroLinksOpenFdRegularFileChild_NoRandomSave) { +// +// It is marked NoSave because we don't support saving unlinked files. +TEST_F(StatTest, ZeroLinksOpenFdRegularFileChild_NoSave) { // Setting the enviornment variable GVISOR_GOFER_UNCACHED to any value // will prevent this test from running, see the tmpfs lifecycle. // @@ -387,9 +397,6 @@ TEST_F(StatTest, ZeroLinksOpenFdRegularFileChild_NoRandomSave) { const char* uncached_gofer = getenv("GVISOR_GOFER_UNCACHED"); SKIP_IF(uncached_gofer != nullptr); - // We don't support saving unlinked files. - const DisableSave ds; - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const TempPath child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( dir.path(), "hello", TempPath::kDefaultFileMode)); @@ -432,6 +439,11 @@ TEST_F(StatTest, ZeroLinksOpenFdRegularFileChild_NoRandomSave) { // Test link counts with a directory as the child. TEST_F(StatTest, LinkCountsWithDirChild) { + // Skip this test if we are testing overlayfs because overlayfs does not + // (intentionally) return the correct nlink value for directories. + // See fs/overlayfs/inode.c:ovl_getattr(). + SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))); + const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Before a child is added the two links are "." and the link from the parent. diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc index 49f2f156c..f0fb166bd 100644 --- a/test/syscalls/linux/statfs.cc +++ b/test/syscalls/linux/statfs.cc @@ -27,10 +27,6 @@ namespace testing { namespace { -// From linux/magic.h. For some reason, not defined in the headers for some -// build environments. -#define OVERLAYFS_SUPER_MAGIC 0x794c7630 - TEST(StatfsTest, CannotStatBadPath) { auto temp_file = NewTempAbsPathInDir("/tmp"); @@ -43,9 +39,6 @@ TEST(StatfsTest, InternalTmpfs) { struct statfs st; EXPECT_THAT(statfs(temp_file.path().c_str(), &st), SyscallSucceeds()); - // Note: We could be an overlay or goferfs on some configurations. - EXPECT_TRUE(st.f_type == TMPFS_MAGIC || st.f_type == OVERLAYFS_SUPER_MAGIC || - st.f_type == V9FS_MAGIC); } TEST(StatfsTest, InternalDevShm) { diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc index f4f6ff490..4d9eba7f0 100644 --- a/test/syscalls/linux/symlink.cc +++ b/test/syscalls/linux/symlink.cc @@ -327,6 +327,16 @@ TEST(SymlinkTest, FollowUpdatesATime) { EXPECT_LT(st_before_follow.st_atime, st_after_follow.st_atime); } +TEST(SymlinkTest, SymlinkAtEmptyPath) { + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY, 0666)); + EXPECT_THAT(symlinkat(file.path().c_str(), fd.get(), ""), + SyscallFailsWithErrno(ENOENT)); +} + class ParamSymlinkTest : public ::testing::TestWithParam<std::string> {}; // Test that creating an existing symlink with creat will create the target. diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index ab731db1d..e0981e28a 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -1643,6 +1643,36 @@ TEST_P(SimpleTcpSocketTest, GetSocketDetachFilter) { SyscallFailsWithErrno(ENOPROTOOPT)); } +TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) { + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + constexpr int kLingerTimeout = 10; // Seconds. + + // Set the SO_LINGER option. + struct linger sl = { + .l_onoff = 1, + .l_linger = kLingerTimeout, + }; + ASSERT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), + SyscallSucceeds()); + + struct pollfd poll_fd = { + .fd = s.get(), + .events = POLLHUP, + }; + constexpr int kPollTimeoutMs = 0; + ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), + SyscallSucceedsWithValue(1)); + + auto const start_time = absl::Now(); + EXPECT_THAT(close(s.release()), SyscallSucceeds()); + auto const end_time = absl::Now(); + + // Close() should not linger and return immediately. + ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout)); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest, ::testing::Values(AF_INET, AF_INET6)); diff --git a/test/syscalls/linux/truncate.cc b/test/syscalls/linux/truncate.cc index c988c6380..bfc95ed38 100644 --- a/test/syscalls/linux/truncate.cc +++ b/test/syscalls/linux/truncate.cc @@ -196,6 +196,26 @@ TEST(TruncateTest, FtruncateNonWriteable) { EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL)); } +// ftruncate(2) should succeed as long as the file descriptor is writeable, +// regardless of whether the file permissions allow writing. +TEST(TruncateTest, FtruncateWithoutWritePermission_NoRandomSave) { + // Drop capabilities that allow us to override file permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + + // The only time we can open a file with flags forbidden by its permissions + // is when we are creating the file. We cannot re-open with the same flags, + // so we cannot restore an fd obtained from such an operation. + const DisableSave ds; + auto path = NewTempAbsPath(); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDWR | O_CREAT, 0444)); + + // In goferfs, ftruncate may be converted to a remote truncate operation that + // unavoidably requires write permission. + SKIP_IF(IsRunningOnGvisor() && !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(path))); + ASSERT_THAT(ftruncate(fd.get(), 100), SyscallSucceeds()); +} + TEST(TruncateTest, TruncateNonExist) { EXPECT_THAT(truncate("/foo/bar", 0), SyscallFailsWithErrno(ENOENT)); } diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 97db2b321..1a7673317 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -14,6 +14,9 @@ #include <arpa/inet.h> #include <fcntl.h> + +#include <ctime> + #ifdef __linux__ #include <linux/errqueue.h> #include <linux/filter.h> @@ -834,8 +837,9 @@ TEST_P(UdpSocketTest, ReceiveBeforeConnect) { // Receive the data. It works because it was sent before the connect. char received[sizeof(buf)]; - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(sizeof(received))); + EXPECT_THAT( + RecvMsgTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/), + IsPosixErrorOkAndHolds(sizeof(received))); EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); // Send again. This time it should not be received. @@ -924,7 +928,9 @@ TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) { SyscallSucceedsWithValue(1)); // We should get the data even though read has been shutdown. - EXPECT_THAT(recv(bind_.get(), received, 2, 0), SyscallSucceedsWithValue(2)); + EXPECT_THAT( + RecvMsgTimeout(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. @@ -1692,9 +1698,9 @@ 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( - recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT), - SyscallSucceedsWithValue(received.size())); + EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(), + 1 /*timeout*/), + IsPosixErrorOkAndHolds(received.size())); } { @@ -1708,9 +1714,9 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) { SyscallSucceedsWithValue(buf.size())); std::vector<char> received(buf.size()); - EXPECT_THAT( - recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT), - SyscallSucceedsWithValue(received.size())); + ASSERT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(), + 1 /*timeout*/), + IsPosixErrorOkAndHolds(received.size())); } } @@ -1779,9 +1785,9 @@ TEST_P(UdpSocketTest, RecvBufLimits) { for (int i = 0; i < sent - 1; i++) { // Receive the data. std::vector<char> received(buf.size()); - EXPECT_THAT( - recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT), - SyscallSucceedsWithValue(received.size())); + EXPECT_THAT(RecvMsgTimeout(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/syscalls/linux/unlink.cc b/test/syscalls/linux/unlink.cc index 2040375c9..061e2e0f1 100644 --- a/test/syscalls/linux/unlink.cc +++ b/test/syscalls/linux/unlink.cc @@ -208,6 +208,20 @@ TEST(RmdirTest, CanRemoveWithTrailingSlashes) { ASSERT_THAT(rmdir(slashslash.c_str()), SyscallSucceeds()); } +TEST(UnlinkTest, UnlinkAtEmptyPath) { + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); + EXPECT_THAT(unlinkat(fd.get(), "", 0), SyscallFailsWithErrno(ENOENT)); + + auto dirInDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); + auto dirFD = ASSERT_NO_ERRNO_AND_VALUE( + Open(dirInDir.path(), O_RDONLY | O_DIRECTORY, 0666)); + EXPECT_THAT(unlinkat(dirFD.get(), "", AT_REMOVEDIR), + SyscallFailsWithErrno(ENOENT)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/vdso_clock_gettime.cc b/test/syscalls/linux/vdso_clock_gettime.cc index ce1899f45..2a8699a7b 100644 --- a/test/syscalls/linux/vdso_clock_gettime.cc +++ b/test/syscalls/linux/vdso_clock_gettime.cc @@ -38,8 +38,6 @@ std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { switch (info.param) { case CLOCK_MONOTONIC: return "CLOCK_MONOTONIC"; - case CLOCK_REALTIME: - return "CLOCK_REALTIME"; case CLOCK_BOOTTIME: return "CLOCK_BOOTTIME"; default: @@ -47,59 +45,36 @@ std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { } } -class CorrectVDSOClockTest : public ::testing::TestWithParam<clockid_t> {}; +class MonotonicVDSOClockTest : public ::testing::TestWithParam<clockid_t> {}; -TEST_P(CorrectVDSOClockTest, IsCorrect) { +TEST_P(MonotonicVDSOClockTest, IsCorrect) { + // The VDSO implementation of clock_gettime() uses the TSC. On KVM, sentry and + // application TSCs can be very desynchronized; see + // sentry/platform/kvm/kvm.vCPU.setSystemTime(). + SKIP_IF(GvisorPlatform() == Platform::kKVM); + + // Check that when we alternate readings from the clock_gettime syscall and + // the VDSO's implementation, we observe the combined sequence as being + // monotonic. struct timespec tvdso, tsys; absl::Time vdso_time, sys_time; - uint64_t total_calls = 0; - - // It is expected that 82.5% of clock_gettime calls will be less than 100us - // skewed from the system time. - // Unfortunately this is not only influenced by the VDSO clock skew, but also - // by arbitrary scheduling delays and the like. The test is therefore - // regularly disabled. - std::map<absl::Duration, std::tuple<double, uint64_t, uint64_t>> confidence = - { - {absl::Microseconds(100), std::make_tuple(0.825, 0, 0)}, - {absl::Microseconds(250), std::make_tuple(0.94, 0, 0)}, - {absl::Milliseconds(1), std::make_tuple(0.999, 0, 0)}, - }; - - absl::Time start = absl::Now(); - while (absl::Now() < start + absl::Seconds(30)) { - EXPECT_THAT(clock_gettime(GetParam(), &tvdso), SyscallSucceeds()); - EXPECT_THAT(syscall(__NR_clock_gettime, GetParam(), &tsys), - SyscallSucceeds()); - + ASSERT_THAT(syscall(__NR_clock_gettime, GetParam(), &tsys), + SyscallSucceeds()); + sys_time = absl::TimeFromTimespec(tsys); + auto end = absl::Now() + absl::Seconds(10); + while (absl::Now() < end) { + ASSERT_THAT(clock_gettime(GetParam(), &tvdso), SyscallSucceeds()); vdso_time = absl::TimeFromTimespec(tvdso); - - for (auto const& conf : confidence) { - std::get<1>(confidence[conf.first]) += - (sys_time - vdso_time) < conf.first; - } - + EXPECT_LE(sys_time, vdso_time); + ASSERT_THAT(syscall(__NR_clock_gettime, GetParam(), &tsys), + SyscallSucceeds()); sys_time = absl::TimeFromTimespec(tsys); - - for (auto const& conf : confidence) { - std::get<2>(confidence[conf.first]) += - (vdso_time - sys_time) < conf.first; - } - - ++total_calls; - } - - for (auto const& conf : confidence) { - EXPECT_GE(std::get<1>(conf.second) / static_cast<double>(total_calls), - std::get<0>(conf.second)); - EXPECT_GE(std::get<2>(conf.second) / static_cast<double>(total_calls), - std::get<0>(conf.second)); + EXPECT_LE(vdso_time, sys_time); } } -INSTANTIATE_TEST_SUITE_P(ClockGettime, CorrectVDSOClockTest, - ::testing::Values(CLOCK_MONOTONIC, CLOCK_REALTIME, - CLOCK_BOOTTIME), +INSTANTIATE_TEST_SUITE_P(ClockGettime, MonotonicVDSOClockTest, + ::testing::Values(CLOCK_MONOTONIC, CLOCK_BOOTTIME), PrintClockId); } // namespace diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc index 5510a87a0..bd3f829c4 100644 --- a/test/syscalls/linux/xattr.cc +++ b/test/syscalls/linux/xattr.cc @@ -232,7 +232,7 @@ TEST_F(XattrTest, XattrOnInvalidFileTypes) { EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM)); } -TEST_F(XattrTest, SetxattrSizeSmallerThanValue) { +TEST_F(XattrTest, SetXattrSizeSmallerThanValue) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; std::vector<char> val = {'a', 'a'}; @@ -247,7 +247,7 @@ TEST_F(XattrTest, SetxattrSizeSmallerThanValue) { EXPECT_EQ(buf, expected_buf); } -TEST_F(XattrTest, SetxattrZeroSize) { +TEST_F(XattrTest, SetXattrZeroSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; char val = 'a'; @@ -259,7 +259,7 @@ TEST_F(XattrTest, SetxattrZeroSize) { EXPECT_EQ(buf, '-'); } -TEST_F(XattrTest, SetxattrSizeTooLarge) { +TEST_F(XattrTest, SetXattrSizeTooLarge) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; @@ -274,7 +274,7 @@ TEST_F(XattrTest, SetxattrSizeTooLarge) { EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); } -TEST_F(XattrTest, SetxattrNullValueAndNonzeroSize) { +TEST_F(XattrTest, SetXattrNullValueAndNonzeroSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 1, /*flags=*/0), @@ -283,7 +283,7 @@ TEST_F(XattrTest, SetxattrNullValueAndNonzeroSize) { EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); } -TEST_F(XattrTest, SetxattrNullValueAndZeroSize) { +TEST_F(XattrTest, SetXattrNullValueAndZeroSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); @@ -291,7 +291,7 @@ TEST_F(XattrTest, SetxattrNullValueAndZeroSize) { EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0)); } -TEST_F(XattrTest, SetxattrValueTooLargeButOKSize) { +TEST_F(XattrTest, SetXattrValueTooLargeButOKSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; std::vector<char> val(XATTR_SIZE_MAX + 1); @@ -307,7 +307,7 @@ TEST_F(XattrTest, SetxattrValueTooLargeButOKSize) { EXPECT_EQ(buf, expected_buf); } -TEST_F(XattrTest, SetxattrReplaceWithSmaller) { +TEST_F(XattrTest, SetXattrReplaceWithSmaller) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; std::vector<char> val = {'a', 'a'}; @@ -322,7 +322,7 @@ TEST_F(XattrTest, SetxattrReplaceWithSmaller) { EXPECT_EQ(buf, expected_buf); } -TEST_F(XattrTest, SetxattrReplaceWithLarger) { +TEST_F(XattrTest, SetXattrReplaceWithLarger) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; std::vector<char> val = {'a', 'a'}; @@ -336,7 +336,7 @@ TEST_F(XattrTest, SetxattrReplaceWithLarger) { EXPECT_EQ(buf, val); } -TEST_F(XattrTest, SetxattrCreateFlag) { +TEST_F(XattrTest, SetXattrCreateFlag) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_CREATE), @@ -347,7 +347,7 @@ TEST_F(XattrTest, SetxattrCreateFlag) { EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0)); } -TEST_F(XattrTest, SetxattrReplaceFlag) { +TEST_F(XattrTest, SetXattrReplaceFlag) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_REPLACE), @@ -359,14 +359,14 @@ TEST_F(XattrTest, SetxattrReplaceFlag) { EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0)); } -TEST_F(XattrTest, SetxattrInvalidFlags) { +TEST_F(XattrTest, SetXattrInvalidFlags) { const char* path = test_file_name_.c_str(); int invalid_flags = 0xff; EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, invalid_flags), SyscallFailsWithErrno(EINVAL)); } -TEST_F(XattrTest, Getxattr) { +TEST_F(XattrTest, GetXattr) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; int val = 1234; @@ -378,7 +378,7 @@ TEST_F(XattrTest, Getxattr) { EXPECT_EQ(buf, val); } -TEST_F(XattrTest, GetxattrSizeSmallerThanValue) { +TEST_F(XattrTest, GetXattrSizeSmallerThanValue) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; std::vector<char> val = {'a', 'a'}; @@ -390,7 +390,7 @@ TEST_F(XattrTest, GetxattrSizeSmallerThanValue) { EXPECT_EQ(buf, '-'); } -TEST_F(XattrTest, GetxattrSizeLargerThanValue) { +TEST_F(XattrTest, GetXattrSizeLargerThanValue) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; char val = 'a'; @@ -405,7 +405,7 @@ TEST_F(XattrTest, GetxattrSizeLargerThanValue) { EXPECT_EQ(buf, expected_buf); } -TEST_F(XattrTest, GetxattrZeroSize) { +TEST_F(XattrTest, GetXattrZeroSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; char val = 'a'; @@ -418,7 +418,7 @@ TEST_F(XattrTest, GetxattrZeroSize) { EXPECT_EQ(buf, '-'); } -TEST_F(XattrTest, GetxattrSizeTooLarge) { +TEST_F(XattrTest, GetXattrSizeTooLarge) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; char val = 'a'; @@ -434,7 +434,7 @@ TEST_F(XattrTest, GetxattrSizeTooLarge) { EXPECT_EQ(buf, expected_buf); } -TEST_F(XattrTest, GetxattrNullValue) { +TEST_F(XattrTest, GetXattrNullValue) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; char val = 'a'; @@ -445,7 +445,7 @@ TEST_F(XattrTest, GetxattrNullValue) { SyscallFailsWithErrno(EFAULT)); } -TEST_F(XattrTest, GetxattrNullValueAndZeroSize) { +TEST_F(XattrTest, GetXattrNullValueAndZeroSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; char val = 'a'; @@ -461,13 +461,13 @@ TEST_F(XattrTest, GetxattrNullValueAndZeroSize) { EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(size)); } -TEST_F(XattrTest, GetxattrNonexistentName) { +TEST_F(XattrTest, GetXattrNonexistentName) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); } -TEST_F(XattrTest, Listxattr) { +TEST_F(XattrTest, ListXattr) { const char* path = test_file_name_.c_str(); const std::string name = "user.test"; const std::string name2 = "user.test2"; @@ -493,7 +493,7 @@ TEST_F(XattrTest, Listxattr) { EXPECT_EQ(got, expected); } -TEST_F(XattrTest, ListxattrNoXattrs) { +TEST_F(XattrTest, ListXattrNoXattrs) { const char* path = test_file_name_.c_str(); std::vector<char> list, expected; @@ -501,13 +501,13 @@ TEST_F(XattrTest, ListxattrNoXattrs) { SyscallSucceedsWithValue(0)); EXPECT_EQ(list, expected); - // Listxattr should succeed if there are no attributes, even if the buffer + // ListXattr should succeed if there are no attributes, even if the buffer // passed in is a nullptr. EXPECT_THAT(listxattr(path, nullptr, sizeof(list)), SyscallSucceedsWithValue(0)); } -TEST_F(XattrTest, ListxattrNullBuffer) { +TEST_F(XattrTest, ListXattrNullBuffer) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); @@ -516,7 +516,7 @@ TEST_F(XattrTest, ListxattrNullBuffer) { SyscallFailsWithErrno(EFAULT)); } -TEST_F(XattrTest, ListxattrSizeTooSmall) { +TEST_F(XattrTest, ListXattrSizeTooSmall) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); @@ -526,7 +526,7 @@ TEST_F(XattrTest, ListxattrSizeTooSmall) { SyscallFailsWithErrno(ERANGE)); } -TEST_F(XattrTest, ListxattrZeroSize) { +TEST_F(XattrTest, ListXattrZeroSize) { const char* path = test_file_name_.c_str(); const char name[] = "user.test"; EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); @@ -615,12 +615,18 @@ TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) { SKIP_IF(IsRunningOnGvisor() && !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_))); - // Setting/Getting in the trusted namespace requires CAP_SYS_ADMIN. - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - const char* path = test_file_name_.c_str(); const char name[] = "trusted.test"; + // Writing to the trusted.* xattr namespace requires CAP_SYS_ADMIN in the root + // user namespace. There's no easy way to check that, other than trying the + // operation and seeing what happens. We'll call removexattr because it's + // simplest. + if (removexattr(path, name) < 0) { + SKIP_IF(errno == EPERM); + FAIL() << "unexpected errno from removexattr: " << errno; + } + // Set. char val = 'a'; size_t size = sizeof(val); |