| #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 |