1
#include "source/common/filesystem/directory_iterator_impl.h"
2

            
3
#include "envoy/common/exception.h"
4

            
5
#include "source/common/common/fmt.h"
6
#include "source/common/common/utility.h"
7

            
8
#include "absl/strings/strip.h"
9

            
10
namespace Envoy {
11
namespace Filesystem {
12

            
13
DirectoryIteratorImpl::DirectoryIteratorImpl(const std::string& directory_path)
14
514
    : directory_path_(absl::StripSuffix(directory_path, "/")),
15
514
      os_sys_calls_(Api::OsSysCallsSingleton::get()) {
16
514
  openDirectory();
17
514
  if (status_.ok()) {
18
513
    nextEntry();
19
513
  } else {
20
1
    entry_ = {"", FileType::Other, absl::nullopt};
21
1
  }
22
514
}
23

            
24
2119
DirectoryIteratorImpl::~DirectoryIteratorImpl() {
25
2119
  if (dir_ != nullptr) {
26
513
    ::closedir(dir_);
27
513
  }
28
2119
}
29

            
30
2499
DirectoryIteratorImpl& DirectoryIteratorImpl::operator++() {
31
2499
  nextEntry();
32
2499
  return *this;
33
2499
}
34

            
35
514
void DirectoryIteratorImpl::openDirectory() {
36
514
  DIR* temp_dir = ::opendir(directory_path_.c_str());
37
514
  dir_ = temp_dir;
38
514
  if (!dir_) {
39
1
    status_ = absl::ErrnoToStatus(errno, fmt::format("unable to open directory {}: {}",
40
1
                                                     directory_path_, errorDetails(errno)));
41
1
  }
42
514
}
43

            
44
3014
void DirectoryIteratorImpl::nextEntry() {
45
3014
  errno = 0;
46
3014
  dirent* entry = ::readdir(dir_);
47

            
48
3014
  if (entry == nullptr) {
49
480
    entry_ = {"", FileType::Other, absl::nullopt};
50
480
    if (errno != 0) {
51
      status_ = absl::ErrnoToStatus(errno, fmt::format("unable to iterate directory {}: {}",
52
                                                       directory_path_, errorDetails(errno)));
53
    }
54
2534
  } else {
55
2534
    auto result = makeEntry(entry->d_name);
56
2534
    status_ = result.status();
57
2534
    if (!status_.ok()) {
58
      // If we failed to stat one file it might have just been deleted,
59
      // keep iterating over the rest of the dir.
60
2
      return nextEntry();
61
2532
    } else {
62
2532
      entry_ = result.value();
63
2532
    }
64
2534
  }
65
3014
}
66

            
67
2535
absl::StatusOr<DirectoryEntry> DirectoryIteratorImpl::makeEntry(absl::string_view filename) const {
68
2535
  const std::string full_path = absl::StrCat(directory_path_, "/", filename);
69
2535
  struct stat stat_buf;
70
2535
  const Api::SysCallIntResult result = os_sys_calls_.stat(full_path.c_str(), &stat_buf);
71
2535
  if (result.return_value_ != 0) {
72
19
    if (result.errno_ == ENOENT) {
73
      // Special case. This directory entity is likely to be a symlink,
74
      // but the reference is broken as the target could not be stat()'ed.
75
      // If we confirm this with an lstat, treat this file entity as
76
      // a regular file, which may be unlink()'ed.
77
19
      if (::lstat(full_path.c_str(), &stat_buf) == 0 && S_ISLNK(stat_buf.st_mode)) {
78
16
        return DirectoryEntry{std::string{filename}, FileType::Regular, absl::nullopt};
79
16
      }
80
19
    }
81
3
    return absl::ErrnoToStatus(
82
3
        result.errno_, fmt::format("unable to stat file: '{}' ({})", full_path, result.errno_));
83
2516
  } else if (S_ISDIR(stat_buf.st_mode)) {
84
1180
    return DirectoryEntry{std::string{filename}, FileType::Directory, absl::nullopt};
85
1769
  } else if (S_ISREG(stat_buf.st_mode)) {
86
1331
    return DirectoryEntry{std::string{filename}, FileType::Regular,
87
1331
                          static_cast<uint64_t>(stat_buf.st_size)};
88
1331
  } else {
89
5
    return DirectoryEntry{std::string{filename}, FileType::Other, absl::nullopt};
90
5
  }
91
2535
}
92

            
93
} // namespace Filesystem
94
} // namespace Envoy