// 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/container/node_hash_set.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));
  absl::node_hash_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