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

            
3
#include <sys/inotify.h>
4

            
5
#include <cstdint>
6
#include <string>
7

            
8
#include "envoy/api/api.h"
9
#include "envoy/common/exception.h"
10
#include "envoy/event/dispatcher.h"
11
#include "envoy/event/file_event.h"
12

            
13
#include "source/common/common/assert.h"
14
#include "source/common/common/fmt.h"
15
#include "source/common/common/thread.h"
16
#include "source/common/common/utility.h"
17

            
18
namespace Envoy {
19
namespace Filesystem {
20

            
21
WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system)
22
10366
    : file_system_(file_system) {
23
10366
  inotify_fd_ = inotify_init1(IN_NONBLOCK);
24
10366
  RELEASE_ASSERT(inotify_fd_ >= 0, "Consider increasing value of fs.inotify.max_user_watches "
25
10366
                                   "and/or fs.inotify.max_user_instances via sysctl");
26
10366
  inotify_event_ = dispatcher.createFileEvent(
27
10366
      inotify_fd_,
28
13112
      [this](uint32_t events) {
29
5102
        ASSERT(events == Event::FileReadyType::Read);
30
5102
        return onInotifyEvent();
31
5102
      },
32
10366
      Event::FileTriggerType::Edge, Event::FileReadyType::Read);
33
10366
}
34

            
35
10366
WatcherImpl::~WatcherImpl() { close(inotify_fd_); }
36

            
37
10483
absl::Status WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb callback) {
38
  // Because of general inotify pain, we always watch the directory that the file lives in,
39
  // and then synthetically raise per file events.
40
10483
  auto result_or_error = file_system_.splitPathFromFilename(path);
41
10483
  RETURN_IF_NOT_OK_REF(result_or_error.status());
42
10482
  const PathSplitResult result = result_or_error.value();
43

            
44
10482
  const uint32_t watch_mask = IN_MODIFY | IN_MOVED_TO;
45
10482
  int watch_fd = inotify_add_watch(inotify_fd_, std::string(result.directory_).c_str(), watch_mask);
46
10482
  if (watch_fd == -1) {
47
2
    return absl::InvalidArgumentError(
48
2
        fmt::format("unable to add filesystem watch for file {}: {}", path, errorDetails(errno)));
49
2
  }
50

            
51
10480
  ENVOY_LOG(debug, "added watch for directory: '{}' file: '{}' fd: {}", result.directory_,
52
10480
            result.file_, watch_fd);
53
10480
  callback_map_[watch_fd].watches_.push_back({std::string(result.file_), events, callback});
54
10480
  return absl::OkStatus();
55
10482
}
56

            
57
282
void WatcherImpl::callAndLogOnError(OnChangedCb& cb, uint32_t events, const std::string& file) {
58
282
  TRY_ASSERT_MAIN_THREAD {
59
282
    const absl::Status status = cb(events);
60
282
    if (!status.ok()) {
61
      // Use ENVOY_LOG_EVERY_POW_2 to avoid log spam if a callback keeps failing.
62
2
      ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' returned error: {}", file,
63
2
                            status.message());
64
2
    }
65
282
  }
66
282
  END_TRY
67
282
  MULTI_CATCH(
68
282
      const std::exception& e,
69
282
      {
70
282
        ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw exception: {}", file,
71
282
                              e.what());
72
282
      },
73
282
      {
74
282
        ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw unknown exception",
75
282
                              file);
76
282
      });
77
282
}
78

            
79
5102
absl::Status WatcherImpl::onInotifyEvent() {
80
10227
  while (true) {
81
    // The buffer needs to be suitably aligned to store the first inotify_event structure.
82
    // If there are multiple events returned by the read call, the kernel is responsible for
83
    // properly aligning subsequent inotify_event structures (per `man inotify`).
84
10227
    alignas(inotify_event) uint8_t buffer[sizeof(inotify_event) + NAME_MAX + 1];
85
10227
    ssize_t rc = read(inotify_fd_, &buffer, sizeof(buffer));
86
10227
    if (rc == -1 && errno == EAGAIN) {
87
5102
      return absl::OkStatus();
88
5102
    }
89
5125
    RELEASE_ASSERT(rc >= 0, "");
90

            
91
5125
    const size_t event_count = rc;
92
5125
    size_t index = 0;
93
10299
    while (index < event_count) {
94
5174
      auto* file_event = reinterpret_cast<inotify_event*>(&buffer[index]);
95
5174
      ASSERT(callback_map_.count(file_event->wd) == 1);
96

            
97
5174
      std::string file;
98
5174
      if (file_event->len > 0) {
99
5169
        file.assign(file_event->name);
100
5169
      }
101

            
102
5174
      ENVOY_LOG(debug, "notification: fd: {} mask: {:x} file: {}", file_event->wd, file_event->mask,
103
5174
                file);
104

            
105
5174
      uint32_t events = 0;
106
5174
      if (file_event->mask & IN_MODIFY) {
107
4640
        events |= Events::Modified;
108
4640
      }
109
5174
      if (file_event->mask & IN_MOVED_TO) {
110
529
        events |= Events::MovedTo;
111
529
      }
112

            
113
5178
      for (FileWatch& watch : callback_map_[file_event->wd].watches_) {
114
5178
        if (watch.events_ & events) {
115
540
          if (watch.file_ == file) {
116
262
            ENVOY_LOG(debug, "matched callback: file: {}", file);
117
262
            callAndLogOnError(watch.cb_, events, file);
118
361
          } else if (watch.file_.empty()) {
119
20
            ENVOY_LOG(debug, "matched callback: directory: {}", file);
120
20
            callAndLogOnError(watch.cb_, events, file);
121
20
          }
122
540
        }
123
5178
      }
124

            
125
5174
      index += sizeof(inotify_event) + file_event->len;
126
5174
    }
127
5125
  }
128
  return absl::OkStatus();
129
5102
}
130

            
131
} // namespace Filesystem
132
} // namespace Envoy