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