mirror of
https://github.com/falcosecurity/falco.git
synced 2025-09-16 14:58:31 +00:00
new(userspace/falco): add utility for handling hot app restarts
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
148
userspace/falco/app/restart_handler.cpp
Normal file
148
userspace/falco/app/restart_handler.cpp
Normal file
@@ -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 <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
88
userspace/falco/app/restart_handler.h
Normal file
88
userspace/falco/app/restart_handler.h
Normal file
@@ -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 <thread>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
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<bool()>;
|
||||
|
||||
/**
|
||||
* @brief A list of files or directories paths to watch.
|
||||
*/
|
||||
using watch_list_t = std::vector<std::string>;
|
||||
|
||||
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<bool> m_stop;
|
||||
std::atomic<bool> m_forced;
|
||||
on_check_t m_on_check;
|
||||
watch_list_t m_watched_dirs;
|
||||
watch_list_t m_watched_files;
|
||||
};
|
||||
}; // namespace app
|
||||
}; // namespace falco
|
Reference in New Issue
Block a user