diff options
Diffstat (limited to 'test/syscalls/linux/getdents.cc')
-rw-r--r-- | test/syscalls/linux/getdents.cc | 539 |
1 files changed, 0 insertions, 539 deletions
diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc deleted file mode 100644 index b147d6181..000000000 --- a/test/syscalls/linux/getdents.cc +++ /dev/null @@ -1,539 +0,0 @@ -// Copyright 2018 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 <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <stddef.h> -#include <stdint.h> -#include <stdio.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/types.h> -#include <syscall.h> -#include <unistd.h> - -#include <map> -#include <string> -#include <unordered_map> -#include <unordered_set> -#include <utility> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::Contains; -using ::testing::IsEmpty; -using ::testing::IsSupersetOf; -using ::testing::Not; -using ::testing::NotNull; - -namespace gvisor { -namespace testing { - -namespace { - -// New Linux dirent format. -struct linux_dirent64 { - uint64_t d_ino; // Inode number - int64_t d_off; // Offset to next linux_dirent64 - unsigned short d_reclen; // NOLINT, Length of this linux_dirent64 - unsigned char d_type; // NOLINT, File type - char d_name[0]; // Filename (null-terminated) -}; - -// Old Linux dirent format. -struct linux_dirent { - unsigned long d_ino; // NOLINT - unsigned long d_off; // NOLINT - unsigned short d_reclen; // NOLINT - char d_name[0]; -}; - -// Wraps a buffer to provide a set of dirents. -// T is the underlying dirent type. -template <typename T> -class DirentBuffer { - public: - // DirentBuffer manages the buffer. - explicit DirentBuffer(size_t size) - : managed_(true), actual_size_(size), reported_size_(size) { - data_ = new char[actual_size_]; - } - - // The buffer is managed externally. - DirentBuffer(char* data, size_t actual_size, size_t reported_size) - : managed_(false), - data_(data), - actual_size_(actual_size), - reported_size_(reported_size) {} - - ~DirentBuffer() { - if (managed_) { - delete[] data_; - } - } - - T* Data() { return reinterpret_cast<T*>(data_); } - - T* Start(size_t read) { - read_ = read; - if (read_) { - return Data(); - } else { - return nullptr; - } - } - - T* Current() { return reinterpret_cast<T*>(&data_[off_]); } - - T* Next() { - size_t new_off = off_ + Current()->d_reclen; - if (new_off >= read_ || new_off >= actual_size_) { - return nullptr; - } - - off_ = new_off; - return Current(); - } - - size_t Size() { return reported_size_; } - - void Reset() { - off_ = 0; - read_ = 0; - memset(data_, 0, actual_size_); - } - - private: - bool managed_; - char* data_; - size_t actual_size_; - size_t reported_size_; - - size_t off_ = 0; - - size_t read_ = 0; -}; - -// Test for getdents/getdents64. -// T is the Linux dirent type. -template <typename T> -class GetdentsTest : public ::testing::Test { - public: - using LinuxDirentType = T; - using DirentBufferType = DirentBuffer<T>; - - protected: - void SetUp() override { - dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(dir_.path(), O_RDONLY | O_DIRECTORY)); - } - - // Must be overridden with explicit specialization. See below. - int SyscallNum(); - - int Getdents(LinuxDirentType* dirp, unsigned int count) { - return RetryEINTR(syscall)(SyscallNum(), fd_.get(), dirp, count); - } - - // Fill directory with num files, named by number starting at 0. - void FillDirectory(size_t num) { - for (size_t i = 0; i < num; i++) { - auto name = JoinPath(dir_.path(), absl::StrCat(i)); - TEST_CHECK(CreateWithContents(name, "").ok()); - } - } - - // Fill directory with a given list of filenames. - void FillDirectoryWithFiles(const std::vector<std::string>& filenames) { - for (const auto& filename : filenames) { - auto name = JoinPath(dir_.path(), filename); - TEST_CHECK(CreateWithContents(name, "").ok()); - } - } - - // Seek to the start of the directory. - PosixError SeekStart() { - constexpr off_t kStartOfFile = 0; - off_t offset = lseek(fd_.get(), kStartOfFile, SEEK_SET); - if (offset < 0) { - return PosixError(errno, absl::StrCat("error seeking to ", kStartOfFile)); - } - if (offset != kStartOfFile) { - return PosixError(EINVAL, absl::StrCat("tried to seek to ", kStartOfFile, - " but got ", offset)); - } - return NoError(); - } - - // Call getdents multiple times, reading all dirents and calling f on each. - // f has the type signature PosixError f(T*). - // If f returns a non-OK error, so does ReadDirents. - template <typename F> - PosixError ReadDirents(DirentBufferType* dirents, F const& f) { - int n; - do { - dirents->Reset(); - - n = Getdents(dirents->Data(), dirents->Size()); - MaybeSave(); - if (n < 0) { - return PosixError(errno, "getdents"); - } - - for (auto d = dirents->Start(n); d; d = dirents->Next()) { - RETURN_IF_ERRNO(f(d)); - } - } while (n > 0); - - return NoError(); - } - - // Call Getdents successively and count all entries. - int ReadAndCountAllEntries(DirentBufferType* dirents) { - int found = 0; - - EXPECT_NO_ERRNO(ReadDirents(dirents, [&](LinuxDirentType* d) { - found++; - return NoError(); - })); - - return found; - } - - private: - TempPath dir_; - FileDescriptor fd_; -}; - -// Multiple template parameters are not allowed, so we must use explicit -// template specialization to set the syscall number. - -// SYS_getdents isn't defined on arm64. -#ifdef __x86_64__ -template <> -int GetdentsTest<struct linux_dirent>::SyscallNum() { - return SYS_getdents; -} -#endif - -template <> -int GetdentsTest<struct linux_dirent64>::SyscallNum() { - return SYS_getdents64; -} - -#ifdef __x86_64__ -// Test both legacy getdents and getdents64 on x86_64. -typedef ::testing::Types<struct linux_dirent, struct linux_dirent64> - GetdentsTypes; -#elif __aarch64__ -// Test only getdents64 on arm64. -typedef ::testing::Types<struct linux_dirent64> GetdentsTypes; -#endif -TYPED_TEST_SUITE(GetdentsTest, GetdentsTypes); - -// N.B. TYPED_TESTs require explicitly using this-> to access members of -// GetdentsTest, since we are inside of a derived class template. - -TYPED_TEST(GetdentsTest, VerifyEntries) { - typename TestFixture::DirentBufferType dirents(1024); - - this->FillDirectory(2); - - // Map of all the entries we expect to find. - std::map<std::string, bool> found; - found["."] = false; - found[".."] = false; - found["0"] = false; - found["1"] = false; - - EXPECT_NO_ERRNO(this->ReadDirents( - &dirents, [&](typename TestFixture::LinuxDirentType* d) { - auto kv = found.find(d->d_name); - EXPECT_NE(kv, found.end()) << "Unexpected file: " << d->d_name; - if (kv != found.end()) { - EXPECT_FALSE(kv->second); - } - found[d->d_name] = true; - return NoError(); - })); - - for (auto& kv : found) { - EXPECT_TRUE(kv.second) << "File not found: " << kv.first; - } -} - -TYPED_TEST(GetdentsTest, VerifyPadding) { - typename TestFixture::DirentBufferType dirents(1024); - - // Create files with names of length 1 through 16. - std::vector<std::string> files; - std::string filename; - for (int i = 0; i < 16; ++i) { - absl::StrAppend(&filename, "a"); - files.push_back(filename); - } - this->FillDirectoryWithFiles(files); - - // We expect to find all the files, plus '.' and '..'. - const int expect_found = 2 + files.size(); - int found = 0; - - EXPECT_NO_ERRNO(this->ReadDirents( - &dirents, [&](typename TestFixture::LinuxDirentType* d) { - EXPECT_EQ(d->d_reclen % 8, 0) - << "Dirent " << d->d_name - << " had reclen that was not byte aligned: " << d->d_name; - found++; - return NoError(); - })); - - // Make sure we found all the files. - EXPECT_EQ(found, expect_found); -} - -// For a small directory, the provided buffer should be large enough -// for all entries. -TYPED_TEST(GetdentsTest, SmallDir) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some actual files. - this->FillDirectory(2); - expect += 2; - - typename TestFixture::DirentBufferType dirents(256); - - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); -} - -// A directory with lots of files requires calling getdents multiple times. -TYPED_TEST(GetdentsTest, LargeDir) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some actual files. - this->FillDirectory(100); - expect += 100; - - typename TestFixture::DirentBufferType dirents(256); - - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); -} - -// If we lie about the size of the buffer, we should still be able to read the -// entries with the available space. -TYPED_TEST(GetdentsTest, PartialBuffer) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some actual files. - this->FillDirectory(100); - expect += 100; - - void* addr = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - ASSERT_NE(addr, MAP_FAILED); - - char* buf = reinterpret_cast<char*>(addr); - - // Guard page - EXPECT_THAT( - mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, PROT_NONE), - SyscallSucceeds()); - - // Limit space in buf to 256 bytes. - buf += kPageSize - 256; - - // Lie about the buffer. Even though we claim the buffer is 1 page, - // we should still get all of the dirents in the first 256 bytes. - typename TestFixture::DirentBufferType dirents(buf, 256, kPageSize); - - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); - - EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()); -} - -// Open many file descriptors, then scan through /proc/self/fd to find and close -// them all. (The latter is commonly used to handle races between fork/execve -// and the creation of unwanted non-O_CLOEXEC file descriptors.) This tests that -// getdents iterates correctly despite mutation of /proc/self/fd. -TYPED_TEST(GetdentsTest, ProcSelfFd) { - constexpr size_t kNfds = 10; - std::unordered_map<int, FileDescriptor> fds; - fds.reserve(kNfds); - for (size_t i = 0; i < kNfds; i++) { - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - fds.emplace(fd.get(), std::move(fd)); - } - - const FileDescriptor proc_self_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/fd", O_RDONLY | O_DIRECTORY)); - - // Make the buffer very small since we want to iterate. - typename TestFixture::DirentBufferType dirents( - 2 * sizeof(typename TestFixture::LinuxDirentType)); - std::unordered_set<int> prev_fds; - while (true) { - dirents.Reset(); - int rv; - ASSERT_THAT(rv = RetryEINTR(syscall)(this->SyscallNum(), proc_self_fd.get(), - dirents.Data(), dirents.Size()), - SyscallSucceeds()); - if (rv == 0) { - break; - } - for (auto* d = dirents.Start(rv); d; d = dirents.Next()) { - int dfd; - if (!absl::SimpleAtoi(d->d_name, &dfd)) continue; - EXPECT_TRUE(prev_fds.insert(dfd).second) - << "Repeated observation of /proc/self/fd/" << dfd; - fds.erase(dfd); - } - } - - // Check that we closed every fd. - EXPECT_THAT(fds, ::testing::IsEmpty()); -} - -// Test that getdents returns ENOTDIR when called on a file. -TYPED_TEST(GetdentsTest, NotDir) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - typename TestFixture::DirentBufferType dirents(256); - EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), - dirents.Size()), - SyscallFailsWithErrno(ENOTDIR)); -} - -// Test that SEEK_SET to 0 causes getdents to re-read the entries. -TYPED_TEST(GetdentsTest, SeekResetsCursor) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some files to the directory. - this->FillDirectory(10); - expect += 10; - - typename TestFixture::DirentBufferType dirents(256); - - // We should get all the expected entries. - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); - - // Seek back to 0. - ASSERT_NO_ERRNO(this->SeekStart()); - - // We should get all the expected entries again. - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); -} - -// Test that getdents() after SEEK_END succeeds. -// This is a regression test for #128. -TYPED_TEST(GetdentsTest, Issue128ProcSeekEnd) { - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self", O_RDONLY | O_DIRECTORY)); - typename TestFixture::DirentBufferType dirents(256); - - ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), - dirents.Size()), - SyscallSucceeds()); -} - -// Some tests using the glibc readdir interface. -TEST(ReaddirTest, OpenDir) { - DIR* dev; - ASSERT_THAT(dev = opendir("/dev"), NotNull()); - EXPECT_THAT(closedir(dev), SyscallSucceeds()); -} - -TEST(ReaddirTest, RootContainsBasicDirectories) { - EXPECT_THAT(ListDir("/", true), - IsPosixErrorOkAndHolds(IsSupersetOf( - {"bin", "dev", "etc", "lib", "proc", "sbin", "usr"}))); -} - -TEST(ReaddirTest, Bug24096713Dev) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev", true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -TEST(ReaddirTest, Bug24096713ProcTid) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE( - ListDir(absl::StrCat("/proc/", syscall(SYS_gettid), "/"), true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -TEST(ReaddirTest, Bug33429925Proc) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc", true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -TEST(ReaddirTest, Bug35110122Root) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/", true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -// Unlink should invalidate getdents cache. -TEST(ReaddirTest, GoneAfterRemoveCache) { - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - std::string name = std::string(Basename(file.path())); - - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true)); - EXPECT_THAT(contents, Contains(name)); - - file.reset(); - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true)); - EXPECT_THAT(contents, Not(Contains(name))); -} - -// Regression test for b/137398511. Rename should invalidate getdents cache. -TEST(ReaddirTest, GoneAfterRenameCache) { - TempPath src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(src.path())); - std::string name = std::string(Basename(file.path())); - - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true)); - EXPECT_THAT(contents, Contains(name)); - - ASSERT_THAT(rename(file.path().c_str(), JoinPath(dst.path(), name).c_str()), - SyscallSucceeds()); - // Release file since it was renamed. dst cleanup will ultimately delete it. - file.release(); - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true)); - EXPECT_THAT(contents, Not(Contains(name))); - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dst.path(), true)); - EXPECT_THAT(contents, Contains(name)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor |