blob: ba087efb7fb66ff5506f78f60239dec5d5a5589b [file]
#include "tlbmc/utils/inotify_watcher.h"
#include <sys/inotify.h>
#include <unistd.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/synchronization/mutex.h"
namespace milotic_tlbmc {
constexpr size_t kEventSize = sizeof(struct inotify_event);
constexpr size_t kEventBufferLength = 4096;
absl::StatusOr<std::shared_ptr<InotifyWatcher>> InotifyWatcher::Create(
std::shared_ptr<boost::asio::io_context> io_context,
EventCallback callback) {
int fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
if (fd < 0) {
return absl::InternalError("Failed to initialize inotify");
}
auto watcher = std::shared_ptr<InotifyWatcher>(
new InotifyWatcher(std::move(io_context), std::move(callback), fd));
watcher->StartAsyncRead();
return watcher;
}
InotifyWatcher::InotifyWatcher(
std::shared_ptr<boost::asio::io_context> io_context, EventCallback callback,
int fd)
: inotify_fd_(fd),
io_context_(std::move(io_context)),
stream_descriptor_(*io_context_, fd),
read_buffer_(kEventBufferLength),
callback_(std::move(callback)) {}
InotifyWatcher::~InotifyWatcher() { Stop(); }
absl::Status InotifyWatcher::WatchDirectory(std::string dir_path,
uint32_t event_mask) {
absl::MutexLock lock(mutex_);
if (stopped_) {
return absl::FailedPreconditionError("InotifyWatcher is stopped");
}
int wd = inotify_add_watch(inotify_fd_, dir_path.c_str(), event_mask);
if (wd < 0) {
return absl::InternalError(
absl::StrCat("Failed to add watch for ", dir_path));
}
watch_descriptors_[wd] = std::move(dir_path);
return absl::OkStatus();
}
void InotifyWatcher::Stop() {
if (stopped_.exchange(true)) {
return;
}
boost::system::error_code ec;
(void)stream_descriptor_.cancel(ec); // NOLINT(bugprone-unused-return-value)
absl::MutexLock lock(mutex_);
(void)stream_descriptor_.close(ec); // NOLINT(bugprone-unused-return-value)
inotify_fd_ = -1;
}
void InotifyWatcher::StartAsyncRead() {
if (stopped_) {
return;
}
auto self = weak_from_this();
stream_descriptor_.async_read_some(
boost::asio::buffer(read_buffer_),
[self](const boost::system::error_code& error,
std::size_t bytes_transferred) {
if (auto shared_self = self.lock()) {
shared_self->HandleRead(error, bytes_transferred);
}
});
}
void InotifyWatcher::HandleRead(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if (error || stopped_) {
return;
}
if (bytes_transferred > read_buffer_.size()) {
return;
}
size_t i = 0;
while (i + kEventSize <= bytes_transferred) {
if (i >= read_buffer_.size()) {
break;
}
struct inotify_event* event =
reinterpret_cast<struct inotify_event*>(&read_buffer_[i]);
if (i + kEventSize + event->len > bytes_transferred) {
break;
}
std::string dir_path;
{
absl::MutexLock lock(mutex_);
auto it = watch_descriptors_.find(event->wd);
if (it != watch_descriptors_.end()) {
dir_path = it->second;
if ((event->mask & IN_IGNORED) != 0) {
watch_descriptors_.erase(it);
}
}
}
// event->len == 0 indicates an event on the watched directory itself (e.g.,
// directory deletion or watch eviction), so we pass an empty filename.
absl::string_view filename = (event->len > 0) ? event->name : "";
callback_(dir_path, filename, event->mask);
i += kEventSize + event->len;
}
StartAsyncRead();
}
} // namespace milotic_tlbmc