gvisor/test/syscalls/linux/getdents.cc

478 lines
13 KiB
C++

// Copyright 2018 Google LLC
//
// 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::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.
template <>
int GetdentsTest<struct linux_dirent>::SyscallNum() {
return SYS_getdents;
}
template <>
int GetdentsTest<struct linux_dirent64>::SyscallNum() {
return SYS_getdents64;
}
// Test both legacy getdents and getdents64.
typedef ::testing::Types<struct linux_dirent, struct linux_dirent64>
GetdentsTypes;
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 betweek 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));
}
// 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()));
}
} // namespace
} // namespace testing
} // namespace gvisor