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
10392
    : file_system_(file_system) {
23
10392
  inotify_fd_ = inotify_init1(IN_NONBLOCK);
24
10392
  RELEASE_ASSERT(inotify_fd_ >= 0, "Consider increasing value of fs.inotify.max_user_watches "
25
10392
                                   "and/or fs.inotify.max_user_instances via sysctl");
26
10392
  inotify_event_ = dispatcher.createFileEvent(
27
10392
      inotify_fd_,
28
13006
      [this](uint32_t events) {
29
4955
        ASSERT(events == Event::FileReadyType::Read);
30
4955
        return onInotifyEvent();
31
4955
      },
32
10392
      Event::FileTriggerType::Edge, Event::FileReadyType::Read);
33
10392
}
34

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

            
37
10509
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
10509
  auto result_or_error = file_system_.splitPathFromFilename(path);
41
10509
  RETURN_IF_NOT_OK_REF(result_or_error.status());
42
10508
  const PathSplitResult result = result_or_error.value();
43

            
44
10508
  const uint32_t watch_mask = IN_MODIFY | IN_MOVED_TO;
45
10508
  int watch_fd = inotify_add_watch(inotify_fd_, std::string(result.directory_).c_str(), watch_mask);
46
10508
  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
10506
  ENVOY_LOG(debug, "added watch for directory: '{}' file: '{}' fd: {}", result.directory_,
52
10506
            result.file_, watch_fd);
53
10506
  callback_map_[watch_fd].watches_.push_back({std::string(result.file_), events, callback});
54
10506
  return absl::OkStatus();
55
10508
}
56

            
57
280
void WatcherImpl::callAndLogOnError(OnChangedCb& cb, uint32_t events, const std::string& file) {
58
280
  TRY_ASSERT_MAIN_THREAD {
59
280
    const absl::Status status = cb(events);
60
280
    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
280
  }
66
280
  END_TRY
67
280
  MULTI_CATCH(
68
280
      const std::exception& e,
69
280
      {
70
280
        ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw exception: {}", file,
71
280
                              e.what());
72
280
      },
73
280
      {
74
280
        ENVOY_LOG_EVERY_POW_2(warn, "Filesystem watch callback for '{}' threw unknown exception",
75
280
                              file);
76
280
      });
77
280
}
78

            
79
4955
absl::Status WatcherImpl::onInotifyEvent() {
80
9946
  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
9946
    alignas(inotify_event) uint8_t buffer[sizeof(inotify_event) + NAME_MAX + 1];
85
9946
    ssize_t rc = read(inotify_fd_, &buffer, sizeof(buffer));
86
9946
    if (rc == -1 && errno == EAGAIN) {
87
4955
      return absl::OkStatus();
88
4955
    }
89
4991
    RELEASE_ASSERT(rc >= 0, "");
90

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

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

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

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

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

            
125
5044
      index += sizeof(inotify_event) + file_event->len;
126
5044
    }
127
4991
  }
128
  return absl::OkStatus();
129
4955
}
130

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