github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/syscalls/linux/getdents.cc (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <dirent.h>
    16  #include <errno.h>
    17  #include <fcntl.h>
    18  #include <stddef.h>
    19  #include <stdint.h>
    20  #include <stdio.h>
    21  #include <string.h>
    22  #include <sys/mman.h>
    23  #include <sys/types.h>
    24  #include <syscall.h>
    25  #include <unistd.h>
    26  
    27  #include <map>
    28  #include <string>
    29  #include <unordered_map>
    30  #include <unordered_set>
    31  #include <utility>
    32  
    33  #include "gmock/gmock.h"
    34  #include "gtest/gtest.h"
    35  #include "absl/container/node_hash_map.h"
    36  #include "absl/container/node_hash_set.h"
    37  #include "absl/strings/numbers.h"
    38  #include "absl/strings/str_cat.h"
    39  #include "test/util/eventfd_util.h"
    40  #include "test/util/file_descriptor.h"
    41  #include "test/util/fs_util.h"
    42  #include "test/util/posix_error.h"
    43  #include "test/util/temp_path.h"
    44  #include "test/util/test_util.h"
    45  
    46  using ::testing::Contains;
    47  using ::testing::IsEmpty;
    48  using ::testing::IsSupersetOf;
    49  using ::testing::Not;
    50  using ::testing::NotNull;
    51  
    52  namespace gvisor {
    53  namespace testing {
    54  
    55  namespace {
    56  
    57  // New Linux dirent format.
    58  struct linux_dirent64 {
    59    uint64_t d_ino;           // Inode number
    60    int64_t d_off;            // Offset to next linux_dirent64
    61    unsigned short d_reclen;  // NOLINT, Length of this linux_dirent64
    62    unsigned char d_type;     // NOLINT, File type
    63    char d_name[0];           // Filename (null-terminated)
    64  };
    65  
    66  // Old Linux dirent format.
    67  struct linux_dirent {
    68    unsigned long d_ino;      // NOLINT
    69    unsigned long d_off;      // NOLINT
    70    unsigned short d_reclen;  // NOLINT
    71    char d_name[0];
    72  };
    73  
    74  // Wraps a buffer to provide a set of dirents.
    75  // T is the underlying dirent type.
    76  template <typename T>
    77  class DirentBuffer {
    78   public:
    79    // DirentBuffer manages the buffer.
    80    explicit DirentBuffer(size_t size)
    81        : managed_(true), actual_size_(size), reported_size_(size) {
    82      data_ = new char[actual_size_];
    83    }
    84  
    85    // The buffer is managed externally.
    86    DirentBuffer(char* data, size_t actual_size, size_t reported_size)
    87        : managed_(false),
    88          data_(data),
    89          actual_size_(actual_size),
    90          reported_size_(reported_size) {}
    91  
    92    ~DirentBuffer() {
    93      if (managed_) {
    94        delete[] data_;
    95      }
    96    }
    97  
    98    T* Data() { return reinterpret_cast<T*>(data_); }
    99  
   100    T* Start(size_t read) {
   101      read_ = read;
   102      if (read_) {
   103        return Data();
   104      } else {
   105        return nullptr;
   106      }
   107    }
   108  
   109    T* Current() { return reinterpret_cast<T*>(&data_[off_]); }
   110  
   111    T* Next() {
   112      size_t new_off = off_ + Current()->d_reclen;
   113      if (new_off >= read_ || new_off >= actual_size_) {
   114        return nullptr;
   115      }
   116  
   117      off_ = new_off;
   118      return Current();
   119    }
   120  
   121    size_t Size() { return reported_size_; }
   122  
   123    void Reset() {
   124      off_ = 0;
   125      read_ = 0;
   126      memset(data_, 0, actual_size_);
   127    }
   128  
   129   private:
   130    bool managed_;
   131    char* data_;
   132    size_t actual_size_;
   133    size_t reported_size_;
   134  
   135    size_t off_ = 0;
   136  
   137    size_t read_ = 0;
   138  };
   139  
   140  // Test for getdents/getdents64.
   141  // T is the Linux dirent type.
   142  template <typename T>
   143  class GetdentsTest : public ::testing::Test {
   144   public:
   145    using LinuxDirentType = T;
   146    using DirentBufferType = DirentBuffer<T>;
   147  
   148   protected:
   149    void SetUp() override {
   150      dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   151      fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(dir_.path(), O_RDONLY | O_DIRECTORY));
   152    }
   153  
   154    // Must be overridden with explicit specialization. See below.
   155    int SyscallNum();
   156  
   157    int Getdents(LinuxDirentType* dirp, unsigned int count) {
   158      return RetryEINTR(syscall)(SyscallNum(), fd_.get(), dirp, count);
   159    }
   160  
   161    // Fill directory with num files, named by number starting at 0.
   162    void FillDirectory(size_t num) {
   163      for (size_t i = 0; i < num; i++) {
   164        auto name = JoinPath(dir_.path(), absl::StrCat(i));
   165        TEST_CHECK(CreateWithContents(name, "").ok());
   166      }
   167    }
   168  
   169    // Fill directory with a given list of filenames.
   170    void FillDirectoryWithFiles(const std::vector<std::string>& filenames) {
   171      for (const auto& filename : filenames) {
   172        auto name = JoinPath(dir_.path(), filename);
   173        TEST_CHECK(CreateWithContents(name, "").ok());
   174      }
   175    }
   176  
   177    // Seek to the start of the directory.
   178    PosixError SeekStart() {
   179      constexpr off_t kStartOfFile = 0;
   180      off_t offset = lseek(fd_.get(), kStartOfFile, SEEK_SET);
   181      if (offset < 0) {
   182        return PosixError(errno, absl::StrCat("error seeking to ", kStartOfFile));
   183      }
   184      if (offset != kStartOfFile) {
   185        return PosixError(EINVAL, absl::StrCat("tried to seek to ", kStartOfFile,
   186                                               " but got ", offset));
   187      }
   188      return NoError();
   189    }
   190  
   191    // Call getdents multiple times, reading all dirents and calling f on each.
   192    // f has the type signature PosixError f(T*).
   193    // If f returns a non-OK error, so does ReadDirents.
   194    template <typename F>
   195    PosixError ReadDirents(DirentBufferType* dirents, F const& f) {
   196      int n;
   197      do {
   198        dirents->Reset();
   199  
   200        n = Getdents(dirents->Data(), dirents->Size());
   201        MaybeSave();
   202        if (n < 0) {
   203          return PosixError(errno, "getdents");
   204        }
   205  
   206        for (auto d = dirents->Start(n); d; d = dirents->Next()) {
   207          RETURN_IF_ERRNO(f(d));
   208        }
   209      } while (n > 0);
   210  
   211      return NoError();
   212    }
   213  
   214    // Call Getdents successively and count all entries.
   215    int ReadAndCountAllEntries(DirentBufferType* dirents) {
   216      int found = 0;
   217  
   218      EXPECT_NO_ERRNO(ReadDirents(dirents, [&](LinuxDirentType* d) {
   219        found++;
   220        return NoError();
   221      }));
   222  
   223      return found;
   224    }
   225  
   226   private:
   227    TempPath dir_;
   228    FileDescriptor fd_;
   229  };
   230  
   231  // Multiple template parameters are not allowed, so we must use explicit
   232  // template specialization to set the syscall number.
   233  
   234  // SYS_getdents isn't defined on arm64.
   235  #ifdef __x86_64__
   236  template <>
   237  int GetdentsTest<struct linux_dirent>::SyscallNum() {
   238    return SYS_getdents;
   239  }
   240  #endif
   241  
   242  template <>
   243  int GetdentsTest<struct linux_dirent64>::SyscallNum() {
   244    return SYS_getdents64;
   245  }
   246  
   247  #ifdef __x86_64__
   248  // Test both legacy getdents and getdents64 on x86_64.
   249  typedef ::testing::Types<struct linux_dirent, struct linux_dirent64>
   250      GetdentsTypes;
   251  #elif __aarch64__
   252  // Test only getdents64 on arm64.
   253  typedef ::testing::Types<struct linux_dirent64> GetdentsTypes;
   254  #endif
   255  TYPED_TEST_SUITE(GetdentsTest, GetdentsTypes);
   256  
   257  // N.B. TYPED_TESTs require explicitly using this-> to access members of
   258  // GetdentsTest, since we are inside of a derived class template.
   259  
   260  TYPED_TEST(GetdentsTest, VerifyEntries) {
   261    typename TestFixture::DirentBufferType dirents(1024);
   262  
   263    this->FillDirectory(2);
   264  
   265    // Map of all the entries we expect to find.
   266    std::map<std::string, bool> found;
   267    found["."] = false;
   268    found[".."] = false;
   269    found["0"] = false;
   270    found["1"] = false;
   271  
   272    EXPECT_NO_ERRNO(this->ReadDirents(
   273        &dirents, [&](typename TestFixture::LinuxDirentType* d) {
   274          auto kv = found.find(d->d_name);
   275          EXPECT_NE(kv, found.end()) << "Unexpected file: " << d->d_name;
   276          if (kv != found.end()) {
   277            EXPECT_FALSE(kv->second);
   278          }
   279          found[d->d_name] = true;
   280          return NoError();
   281        }));
   282  
   283    for (auto& kv : found) {
   284      EXPECT_TRUE(kv.second) << "File not found: " << kv.first;
   285    }
   286  }
   287  
   288  TYPED_TEST(GetdentsTest, VerifyPadding) {
   289    typename TestFixture::DirentBufferType dirents(1024);
   290  
   291    // Create files with names of length 1 through 16.
   292    std::vector<std::string> files;
   293    std::string filename;
   294    for (int i = 0; i < 16; ++i) {
   295      absl::StrAppend(&filename, "a");
   296      files.push_back(filename);
   297    }
   298    this->FillDirectoryWithFiles(files);
   299  
   300    // We expect to find all the files, plus '.' and '..'.
   301    const int expect_found = 2 + files.size();
   302    int found = 0;
   303  
   304    EXPECT_NO_ERRNO(this->ReadDirents(
   305        &dirents, [&](typename TestFixture::LinuxDirentType* d) {
   306          EXPECT_EQ(d->d_reclen % 8, 0)
   307              << "Dirent " << d->d_name
   308              << " had reclen that was not byte aligned: " << d->d_name;
   309          found++;
   310          return NoError();
   311        }));
   312  
   313    // Make sure we found all the files.
   314    EXPECT_EQ(found, expect_found);
   315  }
   316  
   317  // For a small directory, the provided buffer should be large enough
   318  // for all entries.
   319  TYPED_TEST(GetdentsTest, SmallDir) {
   320    // . and .. should be in an otherwise empty directory.
   321    int expect = 2;
   322  
   323    // Add some actual files.
   324    this->FillDirectory(2);
   325    expect += 2;
   326  
   327    typename TestFixture::DirentBufferType dirents(256);
   328  
   329    EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
   330  }
   331  
   332  // A directory with lots of files requires calling getdents multiple times.
   333  TYPED_TEST(GetdentsTest, LargeDir) {
   334    // . and .. should be in an otherwise empty directory.
   335    int expect = 2;
   336  
   337    // Add some actual files.
   338    this->FillDirectory(100);
   339    expect += 100;
   340  
   341    typename TestFixture::DirentBufferType dirents(256);
   342  
   343    EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
   344  }
   345  
   346  // If we lie about the size of the buffer, we should still be able to read the
   347  // entries with the available space.
   348  TYPED_TEST(GetdentsTest, PartialBuffer) {
   349    // . and .. should be in an otherwise empty directory.
   350    int expect = 2;
   351  
   352    // Add some actual files.
   353    this->FillDirectory(100);
   354    expect += 100;
   355  
   356    void* addr = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE,
   357                      MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
   358    ASSERT_NE(addr, MAP_FAILED);
   359  
   360    char* buf = reinterpret_cast<char*>(addr);
   361  
   362    // Guard page
   363    EXPECT_THAT(
   364        mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, PROT_NONE),
   365        SyscallSucceeds());
   366  
   367    // Limit space in buf to 256 bytes.
   368    buf += kPageSize - 256;
   369  
   370    // Lie about the buffer. Even though we claim the buffer is 1 page,
   371    // we should still get all of the dirents in the first 256 bytes.
   372    typename TestFixture::DirentBufferType dirents(buf, 256, kPageSize);
   373  
   374    EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
   375  
   376    EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds());
   377  }
   378  
   379  // Open many file descriptors, then scan through /proc/self/fd to find and close
   380  // them all. (The latter is commonly used to handle races between fork/execve
   381  // and the creation of unwanted non-O_CLOEXEC file descriptors.) This tests that
   382  // getdents iterates correctly despite mutation of /proc/self/fd.
   383  TYPED_TEST(GetdentsTest, ProcSelfFd) {
   384    constexpr size_t kNfds = 10;
   385    absl::node_hash_map<int, FileDescriptor> fds;
   386    fds.reserve(kNfds);
   387    for (size_t i = 0; i < kNfds; i++) {
   388      FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
   389      fds.emplace(fd.get(), std::move(fd));
   390    }
   391  
   392    const FileDescriptor proc_self_fd =
   393        ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/fd", O_RDONLY | O_DIRECTORY));
   394  
   395    // Make the buffer very small since we want to iterate.
   396    typename TestFixture::DirentBufferType dirents(
   397        2 * sizeof(typename TestFixture::LinuxDirentType));
   398    absl::node_hash_set<int> prev_fds;
   399    while (true) {
   400      dirents.Reset();
   401      int rv;
   402      ASSERT_THAT(rv = RetryEINTR(syscall)(this->SyscallNum(), proc_self_fd.get(),
   403                                           dirents.Data(), dirents.Size()),
   404                  SyscallSucceeds());
   405      if (rv == 0) {
   406        break;
   407      }
   408      for (auto* d = dirents.Start(rv); d; d = dirents.Next()) {
   409        int dfd;
   410        if (!absl::SimpleAtoi(d->d_name, &dfd)) continue;
   411        EXPECT_TRUE(prev_fds.insert(dfd).second)
   412            << "Repeated observation of /proc/self/fd/" << dfd;
   413        fds.erase(dfd);
   414      }
   415    }
   416  
   417    // Check that we closed every fd.
   418    EXPECT_THAT(fds, ::testing::IsEmpty());
   419  }
   420  
   421  // Test that getdents returns ENOTDIR when called on a file.
   422  TYPED_TEST(GetdentsTest, NotDir) {
   423    auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   424    auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
   425  
   426    typename TestFixture::DirentBufferType dirents(256);
   427    EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(),
   428                                    dirents.Size()),
   429                SyscallFailsWithErrno(ENOTDIR));
   430  }
   431  
   432  // Test that getdents returns EBADF when called on an opath file.
   433  TYPED_TEST(GetdentsTest, OpathFile) {
   434    SKIP_IF(IsRunningWithVFS1());
   435  
   436    auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   437    auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH));
   438  
   439    typename TestFixture::DirentBufferType dirents(256);
   440    EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(),
   441                                    dirents.Size()),
   442                SyscallFailsWithErrno(EBADF));
   443  }
   444  
   445  // Test that getdents returns EBADF when called on an opath directory.
   446  TYPED_TEST(GetdentsTest, OpathDirectory) {
   447    SKIP_IF(IsRunningWithVFS1());
   448  
   449    auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   450    auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_PATH | O_DIRECTORY));
   451  
   452    typename TestFixture::DirentBufferType dirents(256);
   453    ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(),
   454                                    dirents.Size()),
   455                SyscallFailsWithErrno(EBADF));
   456  }
   457  
   458  // Test that SEEK_SET to 0 causes getdents to re-read the entries.
   459  TYPED_TEST(GetdentsTest, SeekResetsCursor) {
   460    // . and .. should be in an otherwise empty directory.
   461    int expect = 2;
   462  
   463    // Add some files to the directory.
   464    this->FillDirectory(10);
   465    expect += 10;
   466  
   467    typename TestFixture::DirentBufferType dirents(256);
   468  
   469    // We should get all the expected entries.
   470    EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
   471  
   472    // Seek back to 0.
   473    ASSERT_NO_ERRNO(this->SeekStart());
   474  
   475    // We should get all the expected entries again.
   476    EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
   477  }
   478  
   479  // Test that getdents() after SEEK_END succeeds.
   480  // This is a regression test for #128.
   481  TYPED_TEST(GetdentsTest, Issue128ProcSeekEnd) {
   482    auto fd =
   483        ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self", O_RDONLY | O_DIRECTORY));
   484    typename TestFixture::DirentBufferType dirents(256);
   485  
   486    ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds());
   487    ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(),
   488                                    dirents.Size()),
   489                SyscallSucceeds());
   490  }
   491  
   492  // Some tests using the glibc readdir interface.
   493  TEST(ReaddirTest, OpenDir) {
   494    DIR* dev;
   495    ASSERT_THAT(dev = opendir("/dev"), NotNull());
   496    EXPECT_THAT(closedir(dev), SyscallSucceeds());
   497  }
   498  
   499  TEST(ReaddirTest, RootContainsBasicDirectories) {
   500    EXPECT_THAT(ListDir("/", true),
   501                IsPosixErrorOkAndHolds(IsSupersetOf(
   502                    {"bin", "dev", "etc", "lib", "proc", "sbin", "usr"})));
   503  }
   504  
   505  TEST(ReaddirTest, Bug24096713Dev) {
   506    auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev", true));
   507    EXPECT_THAT(contents, Not(IsEmpty()));
   508  }
   509  
   510  TEST(ReaddirTest, Bug24096713ProcTid) {
   511    auto contents = ASSERT_NO_ERRNO_AND_VALUE(
   512        ListDir(absl::StrCat("/proc/", syscall(SYS_gettid), "/"), true));
   513    EXPECT_THAT(contents, Not(IsEmpty()));
   514  }
   515  
   516  TEST(ReaddirTest, Bug33429925Proc) {
   517    auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc", true));
   518    EXPECT_THAT(contents, Not(IsEmpty()));
   519  }
   520  
   521  TEST(ReaddirTest, Bug35110122Root) {
   522    auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/", true));
   523    EXPECT_THAT(contents, Not(IsEmpty()));
   524  }
   525  
   526  // Unlink should invalidate getdents cache.
   527  TEST(ReaddirTest, GoneAfterRemoveCache) {
   528    TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   529    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
   530    std::string name = std::string(Basename(file.path()));
   531  
   532    auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true));
   533    EXPECT_THAT(contents, Contains(name));
   534  
   535    file.reset();
   536  
   537    contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true));
   538    EXPECT_THAT(contents, Not(Contains(name)));
   539  }
   540  
   541  // Regression test for b/137398511. Rename should invalidate getdents cache.
   542  TEST(ReaddirTest, GoneAfterRenameCache) {
   543    TempPath src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   544    TempPath dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   545  
   546    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(src.path()));
   547    std::string name = std::string(Basename(file.path()));
   548  
   549    auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true));
   550    EXPECT_THAT(contents, Contains(name));
   551  
   552    ASSERT_THAT(rename(file.path().c_str(), JoinPath(dst.path(), name).c_str()),
   553                SyscallSucceeds());
   554    // Release file since it was renamed. dst cleanup will ultimately delete it.
   555    file.release();
   556  
   557    contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true));
   558    EXPECT_THAT(contents, Not(Contains(name)));
   559  
   560    contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dst.path(), true));
   561    EXPECT_THAT(contents, Contains(name));
   562  }
   563  
   564  }  // namespace
   565  
   566  }  // namespace testing
   567  }  // namespace gvisor