// Copyright 2022 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include "gtest/gtest.h" #include "absl/base/macros.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" namespace gvisor { namespace testing { namespace { #ifndef CLOSE_RANGE_UNSHARE #define CLOSE_RANGE_UNSHARE (1U << 1) #endif #ifndef CLOSE_RANGE_CLOEXEC #define CLOSE_RANGE_CLOEXEC (1U << 2) #endif #ifndef SYS_close_range #if defined(__x86_64__) || defined(__aarch64__) #define SYS_close_range 436 #else #error "Unknown architecture" #endif #endif // SYS_close_range int close_range(unsigned int first, unsigned int last, unsigned int flags) { return syscall(SYS_close_range, first, last, flags); } class CloseRangeTest : public ::testing::Test { public: void CreateFiles(int num_files) { file_names_.reserve(num_files); for (int i = 0; i < num_files; ++i) { file_names_.push_back(NewTempAbsPath()); int fd; ASSERT_THAT(fd = open(file_names_[i].c_str(), O_CREAT, 0644), SyscallSucceeds()); ASSERT_THAT(close(fd), SyscallSucceeds()); } } void OpenFilesRdwr() { fds_.clear(); fds_.reserve(file_names_.size()); for (std::string &file_name : file_names_) { int fd; ASSERT_THAT(fd = open(file_name.c_str(), O_RDWR), SyscallSucceeds()); fds_.push_back(fd); } } private: void TearDown() override { for (std::string &name : file_names_) { unlink(name.c_str()); } } protected: std::vector file_names_; std::vector fds_; }; // Base test to confirm that all files in contiguous range get closed. TEST_F(CloseRangeTest, ContiguousRange) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = 0; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } } // Test to confirm that a range with files already closed in the range still // closes the remaining files. TEST_F(CloseRangeTest, RangeWithHoles) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = 0; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } EXPECT_THAT(close(fds_[2]), SyscallSucceeds()); EXPECT_THAT(close(fds_[7]), SyscallSucceeds()); EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } } // Test to confirm that closing a range with fds preceding and following the // range leaves those other fds open. TEST_F(CloseRangeTest, RangeInMiddleOfOpenFiles) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = 0; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } size_t slice_start = 4; size_t slice_end = 7; EXPECT_THAT(close_range(fds_[slice_start], fds_[slice_end], flags), SyscallSucceeds()); for (int fd : std::vector(fds_.begin() + slice_start, fds_.begin() + slice_end + 1)) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } for (int fd : std::vector(fds_.begin(), fds_.begin() + slice_start)) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } for (int fd : std::vector(fds_.begin() + slice_end + 1, fds_.end())) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } } // Test to confirm that calling close_range on just one file succeeds. TEST_F(CloseRangeTest, SingleFile) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 1; unsigned int flags = 0; /* */ CreateFiles(num_files_in_range); OpenFilesRdwr(); auto ret = ReadAllFd(fds_[0]); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); EXPECT_THAT(close_range(fds_[0], fds_[0], flags), SyscallSucceeds()); ret = ReadAllFd(fds_[0]); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } // Test to confirm that calling close_range twice on the same range does not // cause errors. TEST_F(CloseRangeTest, CallCloseRangeTwice) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = 0; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); } // Test that using CLOEXEC flag does not close the file for this process. TEST_F(CloseRangeTest, CloexecFlagTest) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = CLOSE_RANGE_CLOEXEC; // CLOEXEC is only available on >=5.11 kernels. SKIP_IF(close_range(1, 0, flags) < 0 && errno == EINVAL); CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } } // Test that using UNSHARE flag still properly closes the files. TEST_F(CloseRangeTest, UnshareFlagTest) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = CLOSE_RANGE_UNSHARE; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } } // Test that using the UNSHARE flag and closing files at the start of the range // still leaves the latter files opened. TEST_F(CloseRangeTest, UnshareFlagAndCloseRangeAtStart) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = CLOSE_RANGE_UNSHARE; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } size_t range_split = 5; EXPECT_THAT(close_range(fds_[0], fds_[range_split - 1], flags), SyscallSucceeds()); for (int fd : std::vector(fds_.begin(), fds_.begin() + range_split)) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } for (int fd : std::vector(fds_.begin() + range_split, fds_.end())) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } } // Test that using the UNSHARE flag and closing files at the end of the range // still leaves the earlier files opened. TEST_F(CloseRangeTest, UnshareFlagAndCloseRangeAtEnd) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = CLOSE_RANGE_UNSHARE; CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } size_t range_split = 5; EXPECT_THAT( close_range(fds_[range_split], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : std::vector(fds_.begin(), fds_.begin() + range_split)) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } for (int fd : std::vector(fds_.begin() + range_split, fds_.end())) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } } // Test that using both CLOEXEC and UNSHARE flags does not close files for this // process. TEST_F(CloseRangeTest, CloexecAndUnshareFlagTest) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE; // CLOEXEC is only available on >=5.11 kernels. SKIP_IF(close_range(1, 0, flags) < 0 && errno == EINVAL); CreateFiles(num_files_in_range); OpenFilesRdwr(); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, IsPosixErrorOkMatcher()); } } // Test that calling with invalid range does not succeed. TEST_F(CloseRangeTest, RangeFirstGreaterThanLast) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = 0; CreateFiles(num_files_in_range); OpenFilesRdwr(); EXPECT_THAT(close_range(fds_[num_files_in_range - 1], fds_[0], flags), SyscallFailsWithErrno(EINVAL)); } // Test that calling with maximum last argument succeeds and closes all fds in // the range. TEST_F(CloseRangeTest, RangeMaxLast) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; unsigned int flags = 0; CreateFiles(num_files_in_range); OpenFilesRdwr(); EXPECT_THAT(close_range(fds_[0], -1, flags), SyscallSucceeds()); for (int fd : fds_) { auto ret = ReadAllFd(fd); EXPECT_THAT(ret, PosixErrorIs(EBADF)); } } // Test that calling with invalid flags does not succeed. TEST_F(CloseRangeTest, InvalidFlags) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); int num_files_in_range = 10; CreateFiles(num_files_in_range); OpenFilesRdwr(); unsigned int flags = CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE | 0xF; EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallFailsWithErrno(EINVAL)); flags = 0xF0; EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallFailsWithErrno(EINVAL)); flags = CLOSE_RANGE_CLOEXEC | 0xF00; EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags), SyscallFailsWithErrno(EINVAL)); } // Test that calling close_range concurrently while creating new files yields // expected results. TEST_F(CloseRangeTest, ConcurrentCalls) { SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS); const int num_files_in_range = 10; const unsigned int flags = CLOSE_RANGE_UNSHARE; const int num_threads = 100; std::unique_ptr threads[num_threads]; CreateFiles(num_files_in_range); OpenFilesRdwr(); auto cr_call = []() { EXPECT_THAT(close_range(num_files_in_range / 2, num_files_in_range + num_threads, flags), SyscallSucceeds()); }; auto open_file_call = []() { auto file = NewTempAbsPath(); EXPECT_THAT(open(file.c_str(), O_CREAT, 0644), SyscallSucceeds()); }; for (int i = 0; i < num_threads; i++) { if (i % 2 == 0) { threads[i] = std::make_unique(cr_call); } else { threads[i] = std::make_unique(open_file_call); } } } } // namespace } // namespace testing } // namespace gvisor