-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Add file size to DirectoryEntry #24176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
60306a9
2a19b95
19c8a58
9cc86ca
331c5af
47323f2
78d3c79
2a6b3ab
6037bb9
99a7632
2ce171d
2eecbfc
4e840cd
5acaaf7
b582141
ae4ae1f
0257b05
de66291
f11bd48
6c3d3b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -169,15 +169,18 @@ struct DirectoryEntry { | |
| // target. For example, if name_ is a symlink to a directory, its file type will be Directory. | ||
| FileType type_; | ||
|
|
||
| // The file size in bytes. 0 for directories or broken symlinks. | ||
| uint64_t size_bytes_; | ||
|
|
||
| bool operator==(const DirectoryEntry& rhs) const { | ||
| return name_ == rhs.name_ && type_ == rhs.type_; | ||
| return name_ == rhs.name_ && type_ == rhs.type_ && size_bytes_ == rhs.size_bytes_; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry if I'm missing but did we test this new equivalence?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We did now. |
||
| } | ||
| }; | ||
|
|
||
| class DirectoryIteratorImpl; | ||
| class DirectoryIterator { | ||
| public: | ||
| DirectoryIterator() : entry_({"", FileType::Other}) {} | ||
| DirectoryIterator() : entry_({"", FileType::Other, 0}) {} | ||
| virtual ~DirectoryIterator() = default; | ||
|
|
||
| const DirectoryEntry& operator*() const { return entry_; } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,7 @@ DirectoryIteratorImpl::DirectoryIteratorImpl(const std::string& directory_path) | |
| fmt::format("unable to open directory {}: {}", directory_path, ::GetLastError())); | ||
| } | ||
|
|
||
| entry_ = {std::string(find_data.cFileName), fileType(find_data)}; | ||
| entry_ = makeEntry(find_data); | ||
| } | ||
|
|
||
| DirectoryIteratorImpl::~DirectoryIteratorImpl() { | ||
|
|
@@ -34,27 +34,29 @@ DirectoryIteratorImpl& DirectoryIteratorImpl::operator++() { | |
| } | ||
|
|
||
| if (ret == 0) { | ||
| entry_ = {"", FileType::Other}; | ||
| entry_ = {"", FileType::Other, 0}; | ||
| } else { | ||
| entry_ = {std::string(find_data.cFileName), fileType(find_data)}; | ||
| entry_ = makeEntry(find_data); | ||
| } | ||
|
|
||
| return *this; | ||
| } | ||
|
|
||
| FileType DirectoryIteratorImpl::fileType(const WIN32_FIND_DATA& find_data) const { | ||
| DirectoryEntry DirectoryIteratorImpl::makeEntry(const WIN32_FIND_DATA& find_data) { | ||
| ULARGE_INTEGER file_size; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this all be in the final else?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call, now that it's unset everywhere else! |
||
| file_size.LowPart = find_data.nFileSizeLow; | ||
| file_size.HighPart = find_data.nFileSizeHigh; | ||
| uint64_t size = static_cast<uint64_t>(file_size.QuadPart); | ||
| if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && | ||
| !(find_data.dwReserved0 & IO_REPARSE_TAG_SYMLINK)) { | ||
| // The file is reparse point and not a symlink, so it can't be | ||
| // a regular file or a directory | ||
| return FileType::Other; | ||
| } | ||
|
|
||
| if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | ||
| return FileType::Directory; | ||
| return {std::string(find_data.cFileName), FileType::Other, size}; | ||
| } else if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | ||
| return {std::string(find_data.cFileName), FileType::Directory, 0}; | ||
| } else { | ||
| return {std::string(find_data.cFileName), FileType::Regular, size}; | ||
| } | ||
|
|
||
| return FileType::Regular; | ||
| } | ||
|
|
||
| } // namespace Filesystem | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,12 @@ | |
| namespace Envoy { | ||
| namespace Filesystem { | ||
|
|
||
| // NOLINTNEXTLINE(readability-identifier-naming) | ||
| void PrintTo(const DirectoryEntry& entry, std::ostream* os) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry if I'm missing something obvious but why NOLINTNEXTLINE? Can you not just have Envoy standard naming?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PrintTo is a weird template override for test output of objects, so it's stuck using Google conventions. (If it was printTo then it wouldn't override.) |
||
| *os << "{name=" << entry.name_ << ", type=" << static_cast<int>(entry.type_) | ||
| << ", size=" << entry.size_bytes_ << "}"; | ||
| } | ||
|
|
||
| class DirectoryTest : public testing::Test { | ||
| public: | ||
| DirectoryTest() : dir_path_(TestEnvironment::temporaryPath("envoy_test")) { | ||
|
|
@@ -43,11 +49,26 @@ class DirectoryTest : public testing::Test { | |
| void addFiles(std::list<std::string> files) { | ||
| for (const std::string& file_name : files) { | ||
| const std::string full_path = dir_path_ + "/" + file_name; | ||
| { const std::ofstream file(full_path); } | ||
| { | ||
| const std::ofstream file(full_path); | ||
| EXPECT_TRUE(file) << "failed to open test file"; | ||
|
jmarantz marked this conversation as resolved.
|
||
| } | ||
| files_to_remove_.push(full_path); | ||
| } | ||
| } | ||
|
|
||
| void addFileWithContents(absl::string_view file_name, absl::string_view contents) { | ||
| const std::string full_path = absl::StrCat(dir_path_, "/", file_name); | ||
| { | ||
| std::ofstream file(full_path); | ||
| EXPECT_TRUE(file) << "failed to open test file"; | ||
| file << contents; | ||
|
jmarantz marked this conversation as resolved.
|
||
| file.close(); | ||
| EXPECT_TRUE(file) << "failed to write to test file"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry ignorance of ostream but if you fail to write will file become null/false?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It becomes "falsy", because it overrides the bool-cast operator. Can't be null or false, because it's not a pointer. |
||
| } | ||
| files_to_remove_.push(full_path); | ||
| } | ||
|
|
||
| void addSymlinks(std::list<std::pair<std::string, std::string>> symlinks) { | ||
| for (const auto& link : symlinks) { | ||
| const std::string target_path = dir_path_ + "/" + link.first; | ||
|
|
@@ -79,7 +100,7 @@ EntrySet getDirectoryContents(const std::string& dir_path, bool recursive) { | |
| std::string subdir_name = entry.name_; | ||
| EntrySet subdir = getDirectoryContents(dir_path + "/" + subdir_name, recursive); | ||
| for (const DirectoryEntry& entry : subdir) { | ||
| ret.insert({subdir_name + "/" + entry.name_, entry.type_}); | ||
| ret.insert({subdir_name + "/" + entry.name_, entry.type_, entry.size_bytes_}); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -91,9 +112,21 @@ TEST_F(DirectoryTest, DirectoryWithOneFile) { | |
| addFiles({"file"}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"file", FileType::Regular}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"file", FileType::Regular, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
||
| TEST_F(DirectoryTest, DirectoryWithOneFileIncludesCorrectFileSize) { | ||
| absl::string_view contents = "hello!"; | ||
| addFileWithContents("file", contents); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"file", FileType::Regular, contents.size()}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
@@ -103,9 +136,9 @@ TEST_F(DirectoryTest, DirectoryWithOneDirectory) { | |
| addSubDirs({"sub_dir"}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"sub_dir", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"sub_dir", FileType::Directory, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
@@ -116,9 +149,9 @@ TEST_F(DirectoryTest, DirectoryWithFileInSubDirectory) { | |
| addFiles({"sub_dir/sub_file"}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"sub_dir", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"sub_dir", FileType::Directory, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
@@ -129,13 +162,13 @@ TEST_F(DirectoryTest, RecursionIntoSubDirectory) { | |
| addFiles({"file", "sub_dir/sub_file"}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"file", FileType::Regular}, | ||
| {"sub_dir", FileType::Directory}, | ||
| {"sub_dir/sub_file", FileType::Regular}, | ||
| {"sub_dir/.", FileType::Directory}, | ||
| {"sub_dir/..", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"file", FileType::Regular, 0}, | ||
| {"sub_dir", FileType::Directory, 0}, | ||
| {"sub_dir/sub_file", FileType::Regular, 0}, | ||
| {"sub_dir/.", FileType::Directory, 0}, | ||
| {"sub_dir/..", FileType::Directory, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, true)); | ||
| } | ||
|
|
@@ -146,24 +179,25 @@ TEST_F(DirectoryTest, DirectoryWithFileAndDirectory) { | |
| addFiles({"file"}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"sub_dir", FileType::Directory}, | ||
| {"file", FileType::Regular}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"sub_dir", FileType::Directory, 0}, | ||
| {"file", FileType::Regular, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
||
| // Test that a symlink to a file has type FileType::Regular | ||
| TEST_F(DirectoryTest, DirectoryWithSymlinkToFile) { | ||
| addFiles({"file"}); | ||
| const absl::string_view contents = "hello"; | ||
| addFileWithContents("file", contents); | ||
| addSymlinks({{"file", "link"}}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"file", FileType::Regular}, | ||
| {"link", FileType::Regular}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"file", FileType::Regular, contents.size()}, | ||
| {"link", FileType::Regular, contents.size()}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
@@ -174,10 +208,10 @@ TEST_F(DirectoryTest, DirectoryWithSymlinkToDirectory) { | |
| addSymlinks({{"sub_dir", "link_dir"}}); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"sub_dir", FileType::Directory}, | ||
| {"link_dir", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"sub_dir", FileType::Directory, 0}, | ||
| {"link_dir", FileType::Directory, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
@@ -189,14 +223,14 @@ TEST_F(DirectoryTest, DirectoryWithBrokenSymlink) { | |
| TestEnvironment::removePath(dir_path_ + "/sub_dir"); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| #ifndef WIN32 | ||
| // On Linux, a broken directory link is simply a symlink to be rm'ed | ||
| {"link_dir", FileType::Regular}, | ||
| {"link_dir", FileType::Regular, 0}, | ||
| #else | ||
| // On Windows, a broken directory link remains a directory link to be rmdir'ed | ||
| {"link_dir", FileType::Directory}, | ||
| {"link_dir", FileType::Directory, 0}, | ||
| #endif | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
|
|
@@ -205,8 +239,8 @@ TEST_F(DirectoryTest, DirectoryWithBrokenSymlink) { | |
| // Test that we can list an empty directory | ||
| TEST_F(DirectoryTest, DirectoryWithEmptyDirectory) { | ||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| } | ||
|
|
@@ -232,18 +266,22 @@ TEST_F(DirectoryTest, Fifo) { | |
| ASSERT_EQ(0, mkfifo(fifo_path.c_str(), 0644)); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {"fifo", FileType::Other}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| {"fifo", FileType::Other, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path_, false)); | ||
| remove(fifo_path.c_str()); | ||
| } | ||
|
|
||
| TEST_F(DirectoryTest, FileTypeTest) { | ||
| auto sys_calls = Api::OsSysCallsSingleton::get(); | ||
| EXPECT_THROW_WITH_REGEX(DirectoryIteratorImpl::fileType("foo", sys_calls), EnvoyException, | ||
| "unable to stat file: 'foo' .*"); | ||
| // This test seems like it should be doable by removing a file after directory | ||
| // iteration begins, but apparently the behavior of that varies per-filesystem | ||
| // (in some cases the absent file is not seen, in others it is). So we test | ||
| // instead by directly calling the private function. | ||
| TEST_F(DirectoryTest, MakeEntryThrowsOnStatFailure) { | ||
| Directory directory(dir_path_); | ||
| EXPECT_THROW_WITH_REGEX(directory.begin().makeEntry("foo"), EnvoyException, | ||
| "unable to stat file: '.*foo' .*"); | ||
| } | ||
| #endif | ||
|
|
||
|
|
@@ -257,8 +295,8 @@ TEST(Directory, DirectoryHasTrailingPathSeparator) { | |
| TestEnvironment::createPath(dir_path); | ||
|
|
||
| const EntrySet expected = { | ||
| {".", FileType::Directory}, | ||
| {"..", FileType::Directory}, | ||
| {".", FileType::Directory, 0}, | ||
| {"..", FileType::Directory, 0}, | ||
| }; | ||
| EXPECT_EQ(expected, getDirectoryContents(dir_path, false)); | ||
| TestEnvironment::removePath(dir_path); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.