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
2151
DirectoryIteratorImpl::~DirectoryIteratorImpl() {
25
2151
  if (dir_ != nullptr) {
26
513
    ::closedir(dir_);
27
513
  }
28
2151
}
29

            
30
2531
DirectoryIteratorImpl& DirectoryIteratorImpl::operator++() {
31
2531
  nextEntry();
32
2531
  return *this;
33
2531
}
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
3046
void DirectoryIteratorImpl::nextEntry() {
45
3046
  errno = 0;
46
3046
  dirent* entry = ::readdir(dir_);
47

            
48
3046
  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
2566
  } else {
55
2566
    auto result = makeEntry(entry->d_name);
56
2566
    status_ = result.status();
57
2566
    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
2564
    } else {
62
2564
      entry_ = result.value();
63
2564
    }
64
2566
  }
65
3046
}
66

            
67
2567
absl::StatusOr<DirectoryEntry> DirectoryIteratorImpl::makeEntry(absl::string_view filename) const {
68
2567
  const std::string full_path = absl::StrCat(directory_path_, "/", filename);
69
2567
  struct stat stat_buf;
70
2567
  const Api::SysCallIntResult result = os_sys_calls_.stat(full_path.c_str(), &stat_buf);
71
2567
  if (result.return_value_ != 0) {
72
8
    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
8
      if (::lstat(full_path.c_str(), &stat_buf) == 0 && S_ISLNK(stat_buf.st_mode)) {
78
5
        return DirectoryEntry{std::string{filename}, FileType::Regular, absl::nullopt};
79
5
      }
80
8
    }
81
3
    return absl::ErrnoToStatus(
82
3
        result.errno_, fmt::format("unable to stat file: '{}' ({})", full_path, result.errno_));
83
2559
  } else if (S_ISDIR(stat_buf.st_mode)) {
84
1227
    return DirectoryEntry{std::string{filename}, FileType::Directory, absl::nullopt};
85
1769
  } else if (S_ISREG(stat_buf.st_mode)) {
86
1327
    return DirectoryEntry{std::string{filename}, FileType::Regular,
87
1327
                          static_cast<uint64_t>(stat_buf.st_size)};
88
1327
  } else {
89
5
    return DirectoryEntry{std::string{filename}, FileType::Other, absl::nullopt};
90
5
  }
91
2567
}
92

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