diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index d6880765..163f1ab0 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -17,6 +17,7 @@ set( FALCO_SOURCES app/app.cpp app/options.cpp + app/restart_handler.cpp app/actions/helpers_generic.cpp app/actions/helpers_inspector.cpp app/actions/configure_interesting_sets.cpp diff --git a/userspace/falco/app/restart_handler.cpp b/userspace/falco/app/restart_handler.cpp new file mode 100644 index 00000000..86e9307b --- /dev/null +++ b/userspace/falco/app/restart_handler.cpp @@ -0,0 +1,148 @@ +/* +Copyright (C) 2023 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +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. +*/ + +#include "restart_handler.h" +#include "signals.h" +#include "../logger.h" + +#include +#include +#include +#include + +void falco::app::restart_handler::trigger() +{ + m_forced.store(true, std::memory_order_release); +} + +bool falco::app::restart_handler::start(std::string& err) +{ + m_inotify_fd = inotify_init(); + if (m_inotify_fd < 0) + { + err = "could not initialize inotify handler"; + return false; + } + + for (const auto& f : m_watched_files) + { + auto wd = inotify_add_watch(m_inotify_fd, f.c_str(), IN_CLOSE_WRITE); + if (wd < 0) + { + err = "could not watch file: " + f; + return false; + } + falco_logger::log(LOG_DEBUG, "Watching file '" + f +"'\n"); + } + + for (const auto &f : m_watched_dirs) + { + auto wd = inotify_add_watch(m_inotify_fd, f.c_str(), IN_CREATE | IN_DELETE); + if (wd < 0) + { + err = "could not watch directory: " + f; + return false; + } + falco_logger::log(LOG_DEBUG, "Watching directory '" + f +"'\n"); + } + + // launch the watcher thread + m_watcher = std::thread(&falco::app::restart_handler::watcher_loop, this); + return true; +} + +void falco::app::restart_handler::stop() +{ + m_stop.store(true, std::memory_order_release); + if (m_watcher.joinable()) + { + m_watcher.join(); + } +} + +void falco::app::restart_handler::watcher_loop() noexcept +{ + if (fcntl(m_inotify_fd, F_SETOWN, gettid()) < 0) + { + falco_logger::log(LOG_ERR, "Failed setting owner on inotify handler"); + return; + } + + fd_set set; + bool forced = false; + bool should_restart = false; + struct timeval timeout; + uint8_t buf[(10 * (sizeof(struct inotify_event) + NAME_MAX + 1))]; + while (!m_stop.load(std::memory_order_acquire)) + { + // wait for inotify events with a certain timeout + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + FD_ZERO(&set); + FD_SET(m_inotify_fd, &set); + auto rv = select(m_inotify_fd + 1, &set, NULL, NULL, &timeout); + if (rv < 0) + { + falco_logger::log(LOG_ERR, "Failed select in inotify watcher"); + return; + } + + // check if there's been a forced restart request + forced = m_forced.load(std::memory_order_acquire); + m_forced.store(false, std::memory_order_release); + + if (rv > 0 || forced) + { + // if new inotify events have been received during the previous + // dry run, even if the dry run was successful, we dismiss + // the restart attempt and perform an additional dry-run for + // safety purpose (the new inotify events may be related to + // bad config/rules files changes). + should_restart = false; + + // if there's date on the inotify fd, consume it + if (rv > 0) + { + auto n = read(m_inotify_fd, buf, sizeof(buf)); + if (n == 0) + { + continue; + } + if (n < 0) + { + falco_logger::log(LOG_ERR, "Failed read in inotify watcher"); + return; + } + } + + // perform a check run check and attempt restarting + if (m_on_check()) + { + should_restart = true; + } + } + + // if the previous dry run was successful, and no new + // inotify events have been received during the dry run, + // then we trigger the restarting signal and quit + if (should_restart) + { + // todo(jasondellaluce): make this a callback too maybe? + g_restart_signal.trigger(); + return; + } + } +} diff --git a/userspace/falco/app/restart_handler.h b/userspace/falco/app/restart_handler.h new file mode 100644 index 00000000..083c7740 --- /dev/null +++ b/userspace/falco/app/restart_handler.h @@ -0,0 +1,88 @@ +/* +Copyright (C) 2023 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +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. +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace falco +{ +namespace app +{ + +/** + * @brief A thread-safe helper for handling hot-reload application restarts. + */ +class restart_handler +{ +public: + /** + * @brief A function that performs safety checks before confirming + * a triggered application restart. Returns true if the application + * can safely be restarted. + */ + using on_check_t = std::function; + + /** + * @brief A list of files or directories paths to watch. + */ + using watch_list_t = std::vector; + + restart_handler( + on_check_t on_check, + const watch_list_t& watch_files = {}, + const watch_list_t& watch_dirs = {}) + : m_inotify_fd(-1), + m_stop(false), + m_forced(false), + m_on_check(on_check), + m_watched_dirs(watch_dirs), + m_watched_files(watch_files) { } + + virtual ~restart_handler() + { + close(m_inotify_fd); + stop(); + } + + restart_handler(restart_handler&&) = default; + restart_handler& operator = (restart_handler&&) = default; + restart_handler(const restart_handler&) = delete; + restart_handler& operator = (const restart_handler&) = delete; + + bool start(std::string& err); + void stop(); + void trigger(); + +private: + void watcher_loop() noexcept; + + int m_inotify_fd; + std::thread m_watcher; + std::atomic m_stop; + std::atomic m_forced; + on_check_t m_on_check; + watch_list_t m_watched_dirs; + watch_list_t m_watched_files; +}; +}; // namespace app +}; // namespace falco