| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the GPL v2 as described in the | ||
| 10 | * included LICENSE.GPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #if __has_include(<sys/inotify.h>) | ||
| 14 | #define HAS_INOTIFY 1 | ||
| 15 | #include <sys/inotify.h> | ||
| 16 | #endif | ||
| 17 | |||
| 18 | #include <cstring> | ||
| 19 | #include <unistd.h> | ||
| 20 | #include <vector> | ||
| 21 | |||
| 22 | #include "FileMonitor.h" | ||
| 23 | #include "Timers.h" | ||
| 24 | #include "common_mini.h" | ||
| 25 | |||
| 26 | class FileMonitorInfo { | ||
| 27 | public: | ||
| 28 | 20940 | FileMonitorInfo() = default; | |
| 29 | 20718 | ~FileMonitorInfo() = default; | |
| 30 | |||
| 31 | 19051 | bool checkForChanges(const std::string& fn) { | |
| 32 | 19051 | uint64_t newTimestamp = FileTimestamp(fn); | |
| 33 |
2/2✓ Branch 0 taken 4946 times.
✓ Branch 1 taken 14105 times.
|
19051 | if (newTimestamp != timestamp) { |
| 34 | 4946 | timestamp = newTimestamp; | |
| 35 | 4946 | return true; // File has changed | |
| 36 | } | ||
| 37 | 14105 | return false; // No change | |
| 38 | } | ||
| 39 | |||
| 40 | uint64_t timestamp = 0; // Last modified timestamp | ||
| 41 | int inotify_watch_fd = -1; // Inotify watch descriptor | ||
| 42 | bool isDirectory = false; // Is this a directory, just looking for create/delete events | ||
| 43 | |||
| 44 | std::map<std::string, std::function<void()>> callbacks; // Callbacks for this file | ||
| 45 | }; | ||
| 46 | |||
| 47 | FileMonitor FileMonitor::INSTANCE; | ||
| 48 | 2346 | FileMonitor::FileMonitor() : | |
| 49 | 2346 | inotify_fd_(-1) { | |
| 50 | #ifdef HAS_INOTIFY | ||
| 51 | 2346 | inotify_fd_ = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); | |
| 52 | #endif | ||
| 53 | 2346 | } | |
| 54 | 458 | FileMonitor::~FileMonitor() { | |
| 55 | #ifdef HAS_INOTIFY | ||
| 56 |
1/2✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
|
458 | if (inotify_fd_ >= 0) { |
| 57 | 458 | close(inotify_fd_); | |
| 58 | 458 | inotify_fd_ = -1; | |
| 59 | } | ||
| 60 | #endif | ||
| 61 | 458 | } | |
| 62 | |||
| 63 | 1885 | void FileMonitor::Initialize(std::map<int, std::function<bool(int)>>& callbacks) { | |
| 64 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | if (inotify_fd_ >= 0) { |
| 65 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
3770 | callbacks[inotify_fd_] = [this](int fd) { |
| 66 | 722 | this->fileChangedEvent(); | |
| 67 | 722 | return false; | |
| 68 | 1885 | }; | |
| 69 | } else { | ||
| 70 | ✗ | Timers::INSTANCE.addPeriodicTimer("FileMonitor", 2000, [this]() { | |
| 71 | ✗ | this->fileChangedEvent(); | |
| 72 | ✗ | }); | |
| 73 | } | ||
| 74 | 1885 | } | |
| 75 | 1867 | void FileMonitor::Cleanup() { | |
| 76 | #ifdef HAS_INOTIFY | ||
| 77 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | std::lock_guard<std::mutex> lock(mutex_); |
| 78 |
2/2✓ Branch 0 taken 6779 times.
✓ Branch 1 taken 1867 times.
|
8646 | for (const auto& entry : fileMapping_) { |
| 79 | 6779 | inotify_rm_watch(inotify_fd_, entry.first); | |
| 80 | } | ||
| 81 | #endif | ||
| 82 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1867 times.
|
1867 | if (inotify_fd_ < 0) { |
| 83 | ✗ | Timers::INSTANCE.stopPeriodicTimer("FileMonitor"); | |
| 84 | } | ||
| 85 | 1867 | fileMapping_.clear(); | |
| 86 | 1867 | files_.clear(); | |
| 87 | 1867 | } | |
| 88 | 20938 | FileMonitor& FileMonitor::AddFile(const std::string& id, const std::string& file, const std::function<void()>& callback, bool modificationsOnly) { | |
| 89 |
1/2✓ Branch 0 taken 20938 times.
✗ Branch 1 not taken.
|
20938 | std::lock_guard<std::mutex> lock(mutex_); |
| 90 | |||
| 91 |
1/2✓ Branch 0 taken 20938 times.
✗ Branch 1 not taken.
|
20938 | auto it = files_.find(file); |
| 92 |
2/2✓ Branch 0 taken 19051 times.
✓ Branch 1 taken 1887 times.
|
20938 | if (it == files_.end()) { |
| 93 |
2/4✓ Branch 0 taken 19051 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19051 times.
✗ Branch 3 not taken.
|
19051 | files_[file].checkForChanges(file); |
| 94 | } | ||
| 95 |
1/2✓ Branch 0 taken 20938 times.
✗ Branch 1 not taken.
|
20938 | auto& fileInfo = files_[file]; |
| 96 |
2/4✓ Branch 0 taken 20938 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20938 times.
✗ Branch 3 not taken.
|
20938 | fileInfo.callbacks[id] = callback; |
| 97 | #ifdef HAS_INOTIFY | ||
| 98 |
2/2✓ Branch 0 taken 19050 times.
✓ Branch 1 taken 1888 times.
|
20938 | if (!modificationsOnly) { |
| 99 |
1/2✓ Branch 0 taken 19050 times.
✗ Branch 1 not taken.
|
19050 | std::string dir = file.substr(0, file.find_last_of("/") + 1); |
| 100 |
1/2✓ Branch 0 taken 19050 times.
✗ Branch 1 not taken.
|
19050 | auto dit = files_.find(dir); |
| 101 |
2/2✓ Branch 0 taken 1889 times.
✓ Branch 1 taken 17161 times.
|
19050 | if (dit == files_.end()) { |
| 102 | // IN_MOVED_TO catches files that appear via an atomic rename | ||
| 103 | // (write-temp-then-rename), which is how config files are written so | ||
| 104 | // a reader never sees a partially-written file. Without it, watchers | ||
| 105 | // on rename-replaced files would silently stop firing. | ||
| 106 | 1889 | int inotify_watch_fd = inotify_add_watch(inotify_fd_, dir.c_str(), IN_CREATE | IN_DELETE | IN_MOVED_TO); | |
| 107 |
2/4✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1889 times.
✗ Branch 3 not taken.
|
1889 | fileMapping_[inotify_watch_fd] = dir; |
| 108 |
1/2✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
|
1889 | files_[dir].inotify_watch_fd = inotify_watch_fd; |
| 109 |
1/2✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
|
1889 | files_[dir].isDirectory = true; // Mark as directory for create/delete events |
| 110 | } | ||
| 111 | 19050 | } | |
| 112 |
7/8✓ Branch 0 taken 20938 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5675 times.
✓ Branch 3 taken 15263 times.
✓ Branch 4 taken 4946 times.
✓ Branch 5 taken 729 times.
✓ Branch 6 taken 4946 times.
✓ Branch 7 taken 15992 times.
|
20938 | if (FileExists(file) && fileInfo.inotify_watch_fd < 0) { |
| 113 | // IN_CLOSE_WRITE, not IN_MODIFY: one save is an open with O_TRUNC and | ||
| 114 | // one or more writes, each its own IN_MODIFY, so every save ran the | ||
| 115 | // callbacks two or more times -- the first time on a truncated file. | ||
| 116 | // A channel output reload per event re-created every output twice. | ||
| 117 | 4946 | int inotify_watch_fd = inotify_add_watch(inotify_fd_, file.c_str(), IN_CLOSE_WRITE); | |
| 118 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4946 times.
|
4946 | if (inotify_watch_fd < 0) { |
| 119 | ✗ | return *this; | |
| 120 | } | ||
| 121 |
2/4✓ Branch 0 taken 4946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4946 times.
✗ Branch 3 not taken.
|
4946 | fileMapping_[inotify_watch_fd] = file; |
| 122 | 4946 | fileInfo.inotify_watch_fd = inotify_watch_fd; | |
| 123 | } | ||
| 124 | #endif | ||
| 125 | 20938 | return *this; | |
| 126 | 20938 | } | |
| 127 | 7449 | FileMonitor& FileMonitor::RemoveFile(const std::string& id, const std::string& file) { | |
| 128 |
1/2✓ Branch 0 taken 7449 times.
✗ Branch 1 not taken.
|
7449 | std::lock_guard<std::mutex> lock(mutex_); |
| 129 |
1/2✓ Branch 0 taken 7449 times.
✗ Branch 1 not taken.
|
7449 | auto it = files_.find(file); |
| 130 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7448 times.
|
7449 | if (it != files_.end()) { |
| 131 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | it->second.callbacks.erase(id); |
| 132 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | if (it->second.callbacks.empty() && !it->second.isDirectory) { |
| 133 | // If no callbacks left, remove the file from monitoring | ||
| 134 | #ifdef HAS_INOTIFY | ||
| 135 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (it->second.inotify_watch_fd >= 0) { |
| 136 | 1 | inotify_rm_watch(inotify_fd_, it->second.inotify_watch_fd); | |
| 137 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | fileMapping_.erase(it->second.inotify_watch_fd); |
| 138 | } | ||
| 139 | #endif | ||
| 140 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | files_.erase(it); |
| 141 | } | ||
| 142 | } | ||
| 143 | 7449 | return *this; | |
| 144 | 7449 | } | |
| 145 | |||
| 146 | // Copy the matching callbacks out and invoke them with mutex_ released. | ||
| 147 | // Callbacks do arbitrary heavyweight work (reloading channel outputs, plugin | ||
| 148 | // teardown); if one crashes, the process exit path runs ~Plugin -> RemoveFile | ||
| 149 | // on this same thread, and holding mutex_ here turns that crash into a | ||
| 150 | // self-deadlocked zombie instead of a clean crash-exit. | ||
| 151 | // `id` empty means "every callback on this file". | ||
| 152 | 16982 | void FileMonitor::fireCallbacks(const std::string& id, const std::string& file) { | |
| 153 | 16982 | std::vector<std::function<void()>> callbacks; | |
| 154 | { | ||
| 155 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | std::lock_guard<std::mutex> lock(mutex_); |
| 156 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | auto it = files_.find(file); |
| 157 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | if (it != files_.end()) { |
| 158 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16982 times.
|
16982 | if (id.empty()) { |
| 159 | ✗ | for (const auto& callback : it->second.callbacks) { | |
| 160 | ✗ | callbacks.push_back(callback.second); | |
| 161 | } | ||
| 162 | } else { | ||
| 163 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | auto cit = it->second.callbacks.find(id); |
| 164 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | if (cit != it->second.callbacks.end()) { |
| 165 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | callbacks.push_back(cit->second); |
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | 16982 | } | |
| 170 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | if (!callbacks.empty()) { |
| 171 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | std::lock_guard<std::mutex> cbLock(callbackMutex_); |
| 172 |
2/2✓ Branch 0 taken 16982 times.
✓ Branch 1 taken 16982 times.
|
33964 | for (auto& callback : callbacks) { |
| 173 |
1/2✓ Branch 0 taken 16982 times.
✗ Branch 1 not taken.
|
16982 | callback(); |
| 174 | } | ||
| 175 | 16982 | } | |
| 176 | 16982 | } | |
| 177 | |||
| 178 | ✗ | FileMonitor& FileMonitor::TriggerFileChanged(const std::string& file) { | |
| 179 | ✗ | fireCallbacks("", file); | |
| 180 | ✗ | return *this; | |
| 181 | } | ||
| 182 | |||
| 183 | 16982 | FileMonitor& FileMonitor::TriggerFileChanged(const std::string& id, const std::string& file) { | |
| 184 | // An empty id would mean "all" to fireCallbacks(); AddFile() allows one, so | ||
| 185 | // keep the two overloads from silently converging on different behaviour. | ||
| 186 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16982 times.
|
16982 | if (id.empty()) { |
| 187 | ✗ | return *this; | |
| 188 | } | ||
| 189 | 16982 | fireCallbacks(id, file); | |
| 190 | 16982 | return *this; | |
| 191 | } | ||
| 192 | |||
| 193 | 722 | void FileMonitor::fileChangedEvent() { | |
| 194 | #ifdef HAS_INOTIFY | ||
| 195 | char buffer[16384]; | ||
| 196 | size_t buffer_i; | ||
| 197 | struct inotify_event* pevent; | ||
| 198 | ssize_t r; | ||
| 199 | size_t event_size; | ||
| 200 | |||
| 201 | 722 | memset(buffer, 0, sizeof(buffer)); | |
| 202 |
1/2✓ Branch 0 taken 722 times.
✗ Branch 1 not taken.
|
722 | r = read(inotify_fd_, buffer, sizeof(buffer)); |
| 203 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 722 times.
|
722 | if (r <= 0) { |
| 204 | ✗ | return; | |
| 205 | } | ||
| 206 | 722 | buffer_i = 0; | |
| 207 |
2/2✓ Branch 0 taken 904 times.
✓ Branch 1 taken 115 times.
|
1019 | while (buffer_i < r) { |
| 208 | /* Parse events and queue them. */ | ||
| 209 | 904 | pevent = (struct inotify_event*)&buffer[buffer_i]; | |
| 210 | 904 | event_size = offsetof(struct inotify_event, name) + pevent->len; | |
| 211 | 904 | buffer_i += event_size; | |
| 212 | |||
| 213 | // Callbacks are collected under the lock but invoked after it is | ||
| 214 | // released -- see TriggerFileChanged for why holding mutex_ across a | ||
| 215 | // callback is not survivable. | ||
| 216 | 904 | std::vector<std::function<void()>> pendingCallbacks; | |
| 217 |
1/2✓ Branch 0 taken 904 times.
✗ Branch 1 not taken.
|
904 | std::unique_lock<std::mutex> lock(mutex_); |
| 218 |
2/4✓ Branch 0 taken 904 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 904 times.
✗ Branch 3 not taken.
|
904 | std::string path = fileMapping_[pevent->wd]; |
| 219 | |||
| 220 | // printf("%d) %s: (%s) id: %d mask: %X\n", buffer_i, path.c_str(), pevent->name, pevent->wd, pevent->mask); | ||
| 221 | |||
| 222 |
2/2✓ Branch 0 taken 118 times.
✓ Branch 1 taken 786 times.
|
904 | if (pevent->mask & IN_IGNORED) { |
| 223 | // The kernel auto-removed this watch -- e.g. the file was deleted, or | ||
| 224 | // it was replaced via an atomic rename (write-temp-then-rename), which | ||
| 225 | // unlinks the old inode. Drop our stale wd mapping and clear the | ||
| 226 | // file's watch descriptor so a subsequent IN_CREATE/IN_MOVED_TO for | ||
| 227 | // the same name re-arms an inode watch on the new file. | ||
| 228 |
1/2✓ Branch 0 taken 118 times.
✗ Branch 1 not taken.
|
118 | auto mit = fileMapping_.find(pevent->wd); |
| 229 |
1/2✓ Branch 0 taken 118 times.
✗ Branch 1 not taken.
|
118 | if (mit != fileMapping_.end()) { |
| 230 |
1/2✓ Branch 0 taken 118 times.
✗ Branch 1 not taken.
|
118 | auto fit = files_.find(mit->second); |
| 231 |
5/6✓ Branch 0 taken 5 times.
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 113 times.
|
118 | if (fit != files_.end() && fit->second.inotify_watch_fd == pevent->wd) { |
| 232 | 5 | fit->second.inotify_watch_fd = -1; | |
| 233 | } | ||
| 234 |
1/2✓ Branch 0 taken 118 times.
✗ Branch 1 not taken.
|
118 | fileMapping_.erase(mit); |
| 235 | } | ||
| 236 | 118 | continue; | |
| 237 |
2/2✓ Branch 0 taken 264 times.
✓ Branch 1 taken 522 times.
|
904 | } else if (pevent->mask & (IN_CREATE | IN_MOVED_TO)) { |
| 238 | // A watched file appeared: either created in place (IN_CREATE) or | ||
| 239 | // atomically renamed into place (IN_MOVED_TO). For a rename over an | ||
| 240 | // existing file the previous inode watch is gone (its IN_IGNORED is | ||
| 241 | // handled above or still queued), so re-arm an IN_CLOSE_WRITE watch on the | ||
| 242 | // current inode before notifying. | ||
| 243 |
1/2✓ Branch 0 taken 264 times.
✗ Branch 1 not taken.
|
264 | path += pevent->name; |
| 244 |
1/2✓ Branch 0 taken 264 times.
✗ Branch 1 not taken.
|
264 | auto it = files_.find(path); |
| 245 |
2/2✓ Branch 0 taken 125 times.
✓ Branch 1 taken 139 times.
|
264 | if (it != files_.end()) { |
| 246 | 125 | bool notify = false; | |
| 247 |
2/2✓ Branch 0 taken 114 times.
✓ Branch 1 taken 11 times.
|
125 | if (pevent->mask & IN_MOVED_TO) { |
| 248 | // New inode in place: drop any stale watch on the replaced | ||
| 249 | // inode (the kernel auto-removes it) and always notify. | ||
| 250 |
2/2✓ Branch 0 taken 112 times.
✓ Branch 1 taken 2 times.
|
114 | if (it->second.inotify_watch_fd >= 0) { |
| 251 |
1/2✓ Branch 0 taken 112 times.
✗ Branch 1 not taken.
|
112 | fileMapping_.erase(it->second.inotify_watch_fd); |
| 252 | 112 | it->second.inotify_watch_fd = -1; | |
| 253 | } | ||
| 254 | 114 | notify = true; | |
| 255 | } | ||
| 256 |
1/2✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
|
125 | if (it->second.inotify_watch_fd < 0) { |
| 257 | 125 | int wd = inotify_add_watch(inotify_fd_, path.c_str(), IN_CLOSE_WRITE); | |
| 258 |
1/2✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
|
125 | if (wd >= 0) { |
| 259 | 125 | it->second.inotify_watch_fd = wd; | |
| 260 |
2/4✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
|
125 | fileMapping_[wd] = path; |
| 261 | } | ||
| 262 | 125 | notify = true; | |
| 263 | } | ||
| 264 |
1/2✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
|
125 | if (notify) { |
| 265 |
2/2✓ Branch 0 taken 132 times.
✓ Branch 1 taken 125 times.
|
257 | for (const auto& callback : it->second.callbacks) { |
| 266 |
1/2✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
|
132 | pendingCallbacks.push_back(callback.second); |
| 267 | } | ||
| 268 | } | ||
| 269 | } | ||
| 270 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 516 times.
|
522 | } else if (pevent->mask & IN_DELETE) { |
| 271 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | path += pevent->name; |
| 272 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | auto it = files_.find(path); |
| 273 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (it != files_.end()) { |
| 274 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (it->second.inotify_watch_fd >= 0) { |
| 275 | ✗ | inotify_rm_watch(inotify_fd_, it->second.inotify_watch_fd); | |
| 276 | ✗ | fileMapping_.erase(it->second.inotify_watch_fd); | |
| 277 | ✗ | it->second.inotify_watch_fd = -1; // Reset watch descriptor | |
| 278 | } | ||
| 279 | // Notify whether or not the file's own watch is still here: | ||
| 280 | // the unlink frees the inode first, so that watch's IN_IGNORED | ||
| 281 | // is usually queued ahead of this and has already cleared it. | ||
| 282 | // Gating on it meant a deleted file was never reported. | ||
| 283 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (const auto& callback : it->second.callbacks) { |
| 284 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | pendingCallbacks.push_back(callback.second); |
| 285 | } | ||
| 286 | } | ||
| 287 |
1/2✓ Branch 0 taken 516 times.
✗ Branch 1 not taken.
|
516 | } else if (pevent->mask & IN_CLOSE_WRITE) { |
| 288 |
1/2✓ Branch 0 taken 516 times.
✗ Branch 1 not taken.
|
516 | auto it = files_.find(path); |
| 289 |
1/2✓ Branch 0 taken 516 times.
✗ Branch 1 not taken.
|
516 | if (it != files_.end()) { |
| 290 |
2/2✓ Branch 0 taken 576 times.
✓ Branch 1 taken 516 times.
|
1092 | for (const auto& callback : it->second.callbacks) { |
| 291 |
1/2✓ Branch 0 taken 576 times.
✗ Branch 1 not taken.
|
576 | pendingCallbacks.push_back(callback.second); |
| 292 | } | ||
| 293 | } | ||
| 294 | } | ||
| 295 |
1/2✓ Branch 0 taken 786 times.
✗ Branch 1 not taken.
|
786 | lock.unlock(); |
| 296 |
2/2✓ Branch 0 taken 644 times.
✓ Branch 1 taken 142 times.
|
786 | if (!pendingCallbacks.empty()) { |
| 297 |
1/2✓ Branch 0 taken 644 times.
✗ Branch 1 not taken.
|
644 | std::lock_guard<std::mutex> cbLock(callbackMutex_); |
| 298 |
2/2✓ Branch 0 taken 711 times.
✓ Branch 1 taken 644 times.
|
1355 | for (auto& callback : pendingCallbacks) { |
| 299 |
1/2✓ Branch 0 taken 711 times.
✗ Branch 1 not taken.
|
711 | callback(); |
| 300 | } | ||
| 301 | 644 | } | |
| 302 |
2/2✓ Branch 0 taken 666 times.
✓ Branch 1 taken 120 times.
|
786 | if (buffer_i >= r) { |
| 303 |
1/2✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
|
666 | r = read(inotify_fd_, buffer, sizeof(buffer)); |
| 304 |
2/2✓ Branch 0 taken 607 times.
✓ Branch 1 taken 59 times.
|
666 | if (r <= 0) { |
| 305 | 607 | break; // No more events to process | |
| 306 | } | ||
| 307 | 59 | buffer_i = 0; | |
| 308 | } | ||
| 309 |
9/9✓ Branch 0 taken 179 times.
✓ Branch 1 taken 118 times.
✓ Branch 2 taken 607 times.
✓ Branch 3 taken 179 times.
✓ Branch 4 taken 118 times.
✓ Branch 5 taken 607 times.
✓ Branch 6 taken 179 times.
✓ Branch 7 taken 118 times.
✓ Branch 8 taken 607 times.
|
2354 | } |
| 310 | #else | ||
| 311 | std::vector<std::function<void()>> pendingCallbacks; | ||
| 312 | { | ||
| 313 | std::lock_guard<std::mutex> lock(mutex_); | ||
| 314 | for (auto& fileEntry : files_) { | ||
| 315 | const std::string& file = fileEntry.first; | ||
| 316 | FileMonitorInfo& fileInfo = fileEntry.second; | ||
| 317 | if (fileInfo.checkForChanges(file)) { | ||
| 318 | for (const auto& callback : fileInfo.callbacks) { | ||
| 319 | pendingCallbacks.push_back(callback.second); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | if (!pendingCallbacks.empty()) { | ||
| 325 | std::lock_guard<std::mutex> cbLock(callbackMutex_); | ||
| 326 | for (auto& callback : pendingCallbacks) { | ||
| 327 | callback(); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | #endif | ||
| 331 | } | ||
| 332 |