void fsevents_monitor::run() { if (stream) return; // parsing paths vector<CFStringRef> dirs; for (string path : paths) { dirs.push_back(CFStringCreateWithCString(NULL, path.c_str(), kCFStringEncodingUTF8)); } if (dirs.size() == 0) return; CFArrayRef pathsToWatch = CFArrayCreate(NULL, reinterpret_cast<const void **> (&dirs[0]), dirs.size(), &kCFTypeArrayCallBacks); FSEventStreamContext *context = new FSEventStreamContext(); context->version = 0; context->info = this; context->retain = nullptr; context->release = nullptr; context->copyDescription = nullptr; FSW_ELOG(_("Creating FSEvent stream...\n")); unique_lock<mutex> run_loop_lock(run_mutex); stream = FSEventStreamCreate(NULL, &fsevents_monitor::fsevents_callback, context, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagFileEvents); if (!stream) throw libfsw_exception(_("Event stream could not be created.")); run_loop = CFRunLoopGetCurrent(); run_loop_lock.unlock(); FSW_ELOG(_("Scheduling stream with run loop...\n")); FSEventStreamScheduleWithRunLoop(stream, run_loop, kCFRunLoopDefaultMode); FSW_ELOG(_("Starting event stream...\n")); FSEventStreamStart(stream); FSW_ELOG(_("Starting run loop...\n")); CFRunLoopRun(); }
void fsevents_monitor::on_stop() { lock_guard<mutex> run_loop_lock(run_mutex); if (!run_loop) throw libfsw_exception(_("run loop is null")); FSW_ELOG(_("Stopping event stream...\n")); FSEventStreamStop(stream); stream = nullptr; FSW_ELOG(_("Stopping run loop...\n")); CFRunLoopStop(run_loop); run_loop = nullptr; }
void inotify_monitor::process_pending_events() { // Remove watches. auto wtd = impl->watches_to_remove.begin(); while (wtd != impl->watches_to_remove.end()) { if (inotify_rm_watch(impl->inotify_monitor_handle, *wtd) != 0) { perror("inotify_rm_watch"); } else { ostringstream log; log << _("Removed: ") << *wtd << "\n"; FSW_ELOG(log.str().c_str()); } impl->watches_to_remove.erase(wtd++); } // Clean up descriptors. auto fd = impl->descriptors_to_remove.begin(); while (fd != impl->descriptors_to_remove.end()) { const string & curr_path = impl->wd_to_path[*fd]; impl->path_to_wd.erase(curr_path); impl->wd_to_path.erase(*fd); impl->watched_descriptors.erase(*fd); impl->descriptors_to_remove.erase(fd++); } }
bool inotify_monitor::add_watch(const string &path, const struct stat &fd_stat) { // TODO: Consider optionally adding the IN_EXCL_UNLINK flag. int inotify_desc = inotify_add_watch(impl->inotify_monitor_handle, path.c_str(), IN_ALL_EVENTS); if (inotify_desc == -1) { perror("inotify_add_watch"); } else { impl->watched_descriptors.insert(inotify_desc); impl->wd_to_path[inotify_desc] = path; impl->path_to_wd[path] = inotify_desc; ostringstream log; log << _("Added: ") << path << "\n"; FSW_ELOG(log.str().c_str()); } return (inotify_desc != -1); }
fsevents_monitor::~fsevents_monitor() { if (stream) { FSW_ELOG(_("Stopping event stream...\n")); FSEventStreamStop(stream); FSW_ELOG(_("Invalidating event stream...\n")); FSEventStreamInvalidate(stream); FSW_ELOG(_("Releasing event stream...\n")); FSEventStreamRelease(stream); } stream = nullptr; }
void poll_monitor::run() { collect_initial_data(); for (;;) { #ifdef HAVE_CXX_MUTEX unique_lock<mutex> run_guard(run_mutex); if (should_stop) break; run_guard.unlock(); #endif FSW_ELOG(_("Done scanning.\n")); sleep(latency < MIN_POLL_LATENCY ? MIN_POLL_LATENCY : latency); time(&curr_time); collect_data(); if (events.size()) { notify_events(events); events.clear(); } } }
void monitor::notify_events(const std::vector<event>& events) const { FSW_MONITOR_NOTIFY_GUARD; // Update the last notification timestamp #ifdef HAVE_INACTIVITY_CALLBACK milliseconds now = duration_cast<milliseconds>( system_clock::now().time_since_epoch()); last_notification.store(now); #endif std::vector<event> filtered_events; for (auto const& event : events) { // Filter flags std::vector<fsw_event_flag> filtered_flags = filter_flags(event); if (filtered_flags.empty()) continue; if (!accept_path(event.get_path())) continue; filtered_events.emplace_back(event.get_path(), event.get_time(), filtered_flags); } if (!filtered_events.empty()) { FSW_ELOG(string_utils::string_from_format(_("Notifying events #: %d.\n"), filtered_events.size()).c_str()); callback(filtered_events, context); } }
void monitor::start() { FSW_MONITOR_RUN_GUARD; if (this->running) return; this->running = true; FSW_MONITOR_RUN_GUARD_UNLOCK; // Fire the inactivity thread std::unique_ptr<std::thread> inactivity_thread; #ifdef HAVE_INACTIVITY_CALLBACK if (fire_idle_event) inactivity_thread.reset( new std::thread(monitor::inactivity_callback, this)); #endif // Fire the monitor run loop. this->run(); // Join the inactivity thread and wait until it stops. FSW_ELOG(_("Inactivity notification thread: joining\n")); if (inactivity_thread) inactivity_thread->join(); FSW_MONITOR_RUN_GUARD_LOCK; this->running = false; this->should_stop = false; FSW_MONITOR_RUN_GUARD_UNLOCK; }
void monitor::stop() { // Stopping a monitor is a cooperative task: the caller request a task to // stop and it's responsibility of each monitor to check for this flag and // timely stop the processing loop. FSW_MONITOR_RUN_GUARD; if (!this->running || this->should_stop) return; FSW_ELOG(_("Stopping the monitor.\n")); this->should_stop = true; on_stop(); }
void monitor::inactivity_callback(monitor *mon) { if (!mon) throw libfsw_exception(_("Callback argument cannot be null.")); FSW_ELOG(_("Inactivity notification thread: starting\n")); for (;;) { std::unique_lock<std::mutex> run_guard(mon->run_mutex); if (mon->should_stop) break; run_guard.unlock(); milliseconds elapsed = duration_cast<milliseconds>(system_clock::now().time_since_epoch()) - mon->last_notification.load(); // Sleep and loop again if sufficient time has not elapsed yet. if (elapsed < mon->get_latency_ms()) { milliseconds to_sleep = mon->get_latency_ms() - elapsed; seconds max_sleep_time(2); std::this_thread::sleep_for( to_sleep > max_sleep_time ? max_sleep_time : to_sleep); continue; } // Build a fake event. time_t curr_time; time(&curr_time); std::vector<event> events; events.push_back({"", curr_time, {NoOp}}); mon->notify_events(events); } FSW_ELOG(_("Inactivity notification thread: exiting\n")); }
void poll_monitor::run() { collect_initial_data(); while (true) { FSW_ELOG(_("Done scanning.\n")); sleep(latency < MIN_POLL_LATENCY ? MIN_POLL_LATENCY : latency); time(&curr_time); collect_data(); if (events.size()) { notify_events(events); events.clear(); } } }
inotify_monitor::~inotify_monitor() { // close inotify watchers for (auto inotify_desc_pair : impl->watched_descriptors) { ostringstream log; log << _("Removing: ") << inotify_desc_pair << "\n"; FSW_ELOG(log.str().c_str()); if (inotify_rm_watch(impl->inotify_monitor_handle, inotify_desc_pair)) { perror("inotify_rm_watch"); } } // close inotify if (impl->inotify_monitor_handle > 0) { close(impl->inotify_monitor_handle); } delete impl; }
void inotify_monitor::run() { char buffer[BUFFER_SIZE]; while (true) { process_pending_events(); scan_root_paths(); // If no files can be watched, sleep and repeat the loop. if (!impl->watched_descriptors.size()) { sleep(latency); continue; } // Use select to timeout on file descriptor read the amount specified by // the monitor latency. This way, the monitor has a chance to update its // watches with at least the periodicity expected by the user. fd_set set; struct timeval timeout; FD_ZERO(&set); FD_SET(impl->inotify_monitor_handle, &set); double sec; double frac = modf(this->latency, &sec); timeout.tv_sec = sec; timeout.tv_usec = 1000 * 1000 * frac; int rv = select(impl->inotify_monitor_handle + 1, &set, nullptr, nullptr, &timeout); if (rv == -1) { throw libfsw_exception(_("::select() on inotify descriptor encountered an error.")); } // In case of read timeout just repeat the loop. if (rv == 0) { continue; } ssize_t record_num = read(impl->inotify_monitor_handle, buffer, BUFFER_SIZE); { ostringstream log; log << _("Number of records: ") << record_num << "\n"; FSW_ELOG(log.str().c_str()); } if (!record_num) { throw libfsw_exception(_("read() on inotify descriptor read 0 records.")); } if (record_num == -1) { perror("read()"); throw libfsw_exception(_("read() on inotify descriptor returned -1.")); } time(&impl->curr_time); for (char *p = buffer; p < buffer + record_num;) { struct inotify_event * event = reinterpret_cast<struct inotify_event *> (p); preprocess_event(event); p += (sizeof (struct inotify_event)) + event->len; } if (impl->events.size()) { notify_events(impl->events); impl->events.clear(); } sleep(latency); } }
void inotify_monitor::preprocess_node_event(struct inotify_event * event) { vector<fsw_event_flag> flags; if (event->mask & IN_ACCESS) flags.push_back(fsw_event_flag::PlatformSpecific); if (event->mask & IN_ATTRIB) flags.push_back(fsw_event_flag::AttributeModified); if (event->mask & IN_CLOSE_NOWRITE) flags.push_back(fsw_event_flag::PlatformSpecific); if (event->mask & IN_CLOSE_WRITE) flags.push_back(fsw_event_flag::Updated); if (event->mask & IN_CREATE) flags.push_back(fsw_event_flag::Created); if (event->mask & IN_DELETE) flags.push_back(fsw_event_flag::Removed); if (event->mask & IN_MODIFY) flags.push_back(fsw_event_flag::Updated); if (event->mask & IN_MOVED_FROM) { flags.push_back(fsw_event_flag::Removed); flags.push_back(fsw_event_flag::MovedFrom); } if (event->mask & IN_MOVED_TO) { flags.push_back(fsw_event_flag::Created); flags.push_back(fsw_event_flag::MovedTo); } if (event->mask & IN_OPEN) flags.push_back(fsw_event_flag::PlatformSpecific); // Build the file name. ostringstream filename_stream; filename_stream << impl->wd_to_path[event->wd]; if (event->len > 1) { filename_stream << "/"; filename_stream << event->name; } if (flags.size()) { impl->events.push_back({filename_stream.str(), impl->curr_time, flags}); } { ostringstream log; log << _("Generic event: ") << event->wd << "::" << filename_stream.str() << "\n"; FSW_ELOG(log.str().c_str()); } /* * inotify automatically removes the watch of a watched item that has been * removed and posts an IN_IGNORED event after an IN_DELETE_SELF. */ if (event->mask & IN_IGNORED) { ostringstream log; log << "IN_IGNORED: " << event->wd << "::" << filename_stream.str() << "\n"; FSW_ELOG(log.str().c_str()); impl->descriptors_to_remove.insert(event->wd); } /* * inotify sends an IN_MOVE_SELF event when a watched object is moved into * the same filesystem and keeps watching it. Since its path has changed, * we remove the watch so that recreation is attempted at the next * iteration. * * Beware that a race condition exists which may result in events go * unnoticed when a watched file x is removed and a new file named x is * created thereafter. In this case, fswatch could be blocked on read and * it would not have any chance to create a new watch descriptor for x until * an event is received and read unblocks. */ if (event->mask & IN_MOVE_SELF) { ostringstream log; log << "IN_MOVE_SELF: " << event->wd << "::" << filename_stream.str() << "\n"; FSW_ELOG(log.str().c_str()); impl->watches_to_remove.insert(event->wd); impl->descriptors_to_remove.insert(event->wd); } /* * An file could be moved to a path which is being observed. The clobbered * file is handled by the corresponding IN_DELETE_SELF event. */ /* * inotify automatically removes the watch of the object the IN_DELETE_SELF * event is related to. */ if (event->mask & IN_DELETE_SELF) { ostringstream log; log << "IN_DELETE_SELF: " << event->wd << "::" << filename_stream.str() << "\n"; FSW_ELOG(log.str().c_str()); impl->descriptors_to_remove.insert(event->wd); } }