--- /dev/null
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2020 ScyllaDB Ltd.
+ */
+
+#include <seastar/core/internal/pollable_fd.hh>
+#include <seastar/core/posix.hh>
+#include <seastar/core/reactor.hh>
+#include "fsnotify.hh"
+
+class seastar::fsnotifier::impl : public enable_shared_from_this<impl> {
+ class my_poll_fd : public pollable_fd {
+ public:
+ using pollable_fd::pollable_fd;
+ using pollable_fd::get_fd;
+
+ operator int() const {
+ return get_fd();
+ }
+ };
+ my_poll_fd _fd;
+ watch_token _close_dummy = -1;
+public:
+ impl()
+ : _fd(file_desc::inotify_init(IN_NONBLOCK | IN_CLOEXEC))
+ {}
+ void remove_watch(watch_token);
+ future<watch_token> create_watch(const sstring& path, flags events);
+ future<std::vector<event>> wait();
+ void shutdown();
+ bool active() const {
+ return bool(_fd);
+ }
+};
+
+void seastar::fsnotifier::impl::remove_watch(watch_token token) {
+ if (active()) {
+ auto res = ::inotify_rm_watch(_fd, token);
+ // throw if any other error than EINVAL.
+ throw_system_error_on(res == -1 && errno != EINVAL, "could not remove inotify watch");
+ }
+}
+
+seastar::future<seastar::fsnotifier::watch_token> seastar::fsnotifier::impl::create_watch(const sstring& path, flags events) {
+ if (!active()) {
+ throw std::runtime_error("attempting to use closed notifier");
+ }
+ return engine().inotify_add_watch(_fd, path, uint32_t(events));
+}
+
+seastar::future<std::vector<seastar::fsnotifier::event>> seastar::fsnotifier::impl::wait() {
+ // be paranoid about buffer alignment
+ auto buf = temporary_buffer<char>::aligned(std::max(alignof(::inotify_event), alignof(int64_t)), 4096);
+ auto f = _fd.read_some(buf.get_write(), buf.size());
+ return f.then([me = shared_from_this(), buf = std::move(buf)](size_t n) {
+ auto p = buf.get();
+ auto e = buf.get() + n;
+
+ std::vector<event> events;
+
+ while (p < e) {
+ auto ev = reinterpret_cast<const ::inotify_event*>(p);
+ if (ev->wd == me->_close_dummy && me->_close_dummy != -1) {
+ me->_fd.close();
+ } else {
+ events.emplace_back(event {
+ ev->wd, flags(ev->mask), ev->cookie,
+ ev->len != 0 ? sstring(ev->name) : sstring{}
+ });
+ }
+ p += sizeof(::inotify_event) + ev->len;
+ }
+
+ return events;
+ });
+}
+
+void seastar::fsnotifier::impl::shutdown() {
+ // reactor does not yet have
+ // any means of "shutting down" a non-socket read,
+ // so we work around this by creating a watch for something ubiquitous,
+ // then removing the watch while adding a mark.
+ // This will cause any event waiter to wake up, but ignore the event for our
+ // dummy.
+ (void)create_watch("/", flags::delete_self).then([me = shared_from_this()](watch_token t) {
+ me->_close_dummy = t;
+ me->remove_watch(t);
+ });
+}
+
+seastar::fsnotifier::watch::~watch() {
+ if (_impl) {
+ _impl->remove_watch(_token);
+ }
+}
+
+seastar::fsnotifier::watch::watch(watch&&) noexcept = default;
+seastar::fsnotifier::watch& seastar::fsnotifier::watch::operator=(watch&&) noexcept = default;
+
+seastar::fsnotifier::watch_token seastar::fsnotifier::watch::release() {
+ _impl = {};
+ return _token;
+}
+
+seastar::fsnotifier::watch::watch(shared_ptr<impl> impl, watch_token token)
+ : _token(token)
+ , _impl(std::move(impl))
+{}
+
+seastar::fsnotifier::fsnotifier()
+ : _impl(make_shared<impl>())
+{}
+
+seastar::fsnotifier::~fsnotifier() = default;
+
+seastar::fsnotifier::fsnotifier(fsnotifier&&) = default;
+seastar::fsnotifier& seastar::fsnotifier::operator=(fsnotifier&&) = default;
+
+seastar::future<seastar::fsnotifier::watch> seastar::fsnotifier::create_watch(const sstring& path, flags events) {
+ return _impl->create_watch(path, events).then([this](watch_token token) {
+ return watch(_impl, token);
+ });
+}
+
+seastar::future<std::vector<seastar::fsnotifier::event>> seastar::fsnotifier::wait() const {
+ return _impl->wait();
+}
+
+void seastar::fsnotifier::shutdown() {
+ _impl->shutdown();
+}
+
+bool seastar::fsnotifier::active() const {
+ return _impl->active();
+}