source: imaps-frontend/node_modules/@parcel/watcher/src/kqueue/KqueueBackend.cc@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 8.4 KB
Line 
1#include <memory>
2#include <poll.h>
3#include <unistd.h>
4#include <libgen.h>
5#include <dirent.h>
6#include <fcntl.h>
7#include <sys/stat.h>
8#include "KqueueBackend.hh"
9
10#if __APPLE__
11#define st_mtim st_mtimespec
12#endif
13
14#if !defined(O_EVTONLY)
15#define O_EVTONLY O_RDONLY
16#endif
17
18#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
19
20void KqueueBackend::start() {
21 if ((mKqueue = kqueue()) < 0) {
22 throw std::runtime_error(std::string("Unable to open kqueue: ") + strerror(errno));
23 }
24
25 // Create a pipe that we will write to when we want to end the thread.
26 int err = pipe(mPipe);
27 if (err == -1) {
28 throw std::runtime_error(std::string("Unable to open pipe: ") + strerror(errno));
29 }
30
31 // Subscribe kqueue to this pipe.
32 struct kevent ev;
33 EV_SET(
34 &ev,
35 mPipe[0],
36 EVFILT_READ,
37 EV_ADD | EV_CLEAR,
38 0,
39 0,
40 0
41 );
42
43 if (kevent(mKqueue, &ev, 1, NULL, 0, 0)) {
44 close(mPipe[0]);
45 close(mPipe[1]);
46 throw std::runtime_error(std::string("Unable to watch pipe: ") + strerror(errno));
47 }
48
49 notifyStarted();
50
51 struct kevent events[128];
52
53 while (true) {
54 int event_count = kevent(mKqueue, NULL, 0, events, 128, 0);
55 if (event_count < 0 || events[0].flags == EV_ERROR) {
56 throw std::runtime_error(std::string("kevent error: ") + strerror(errno));
57 }
58
59 // Track all of the watchers that are touched so we can notify them at the end of the events.
60 std::unordered_set<WatcherRef> watchers;
61
62 for (int i = 0; i < event_count; i++) {
63 int flags = events[i].fflags;
64 int fd = events[i].ident;
65 if (fd == mPipe[0]) {
66 // pipe was written to. break out of the loop.
67 goto done;
68 }
69
70 auto it = mFdToEntry.find(fd);
71 if (it == mFdToEntry.end()) {
72 // If fd wasn't in our map, we may have already stopped watching it. Ignore the event.
73 continue;
74 }
75
76 DirEntry *entry = it->second;
77
78 if (flags & NOTE_WRITE && entry && entry->isDir) {
79 // If a write occurred on a directory, we have to diff the contents of that
80 // directory to determine what file was added/deleted.
81 compareDir(fd, entry->path, watchers);
82 } else {
83 std::vector<KqueueSubscription *> subs = findSubscriptions(entry->path);
84 for (auto it = subs.begin(); it != subs.end(); it++) {
85 KqueueSubscription *sub = *it;
86 if (flags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) {
87 sub->watcher->mEvents.remove(sub->path);
88 sub->tree->remove(sub->path);
89 mFdToEntry.erase((int)(size_t)entry->state);
90 mSubscriptions.erase(sub->path);
91 } else if (flags & (NOTE_WRITE | NOTE_ATTRIB | NOTE_EXTEND)) {
92 struct stat st;
93 lstat(sub->path.c_str(), &st);
94 if (entry->mtime != CONVERT_TIME(st.st_mtim)) {
95 entry->mtime = CONVERT_TIME(st.st_mtim);
96 sub->watcher->mEvents.update(sub->path);
97 }
98 }
99
100 watchers.insert(sub->watcher);
101 }
102 }
103 }
104
105 for (auto it = watchers.begin(); it != watchers.end(); it++) {
106 (*it)->notify();
107 }
108 }
109
110done:
111 close(mPipe[0]);
112 close(mPipe[1]);
113 mEndedSignal.notify();
114}
115
116KqueueBackend::~KqueueBackend() {
117 write(mPipe[1], "X", 1);
118 mEndedSignal.wait();
119}
120
121void KqueueBackend::subscribe(WatcherRef watcher) {
122 // Build a full directory tree recursively, and watch each directory.
123 std::shared_ptr<DirTree> tree = getTree(watcher);
124
125 for (auto it = tree->entries.begin(); it != tree->entries.end(); it++) {
126 bool success = watchDir(watcher, it->second.path, tree);
127 if (!success) {
128 throw WatcherError(std::string("error watching " + watcher->mDir + ": " + strerror(errno)), watcher);
129 }
130 }
131}
132
133bool KqueueBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree) {
134 if (watcher->isIgnored(path)) {
135 return false;
136 }
137
138 DirEntry *entry = tree->find(path);
139 if (!entry) {
140 return false;
141 }
142
143 KqueueSubscription sub = {
144 .watcher = watcher,
145 .path = path,
146 .tree = tree
147 };
148
149 if (!entry->state) {
150 int fd = open(path.c_str(), O_EVTONLY);
151 if (fd <= 0) {
152 return false;
153 }
154
155 struct kevent event;
156 EV_SET(
157 &event,
158 fd,
159 EVFILT_VNODE,
160 EV_ADD | EV_CLEAR | EV_ENABLE,
161 NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
162 0,
163 0
164 );
165
166 if (kevent(mKqueue, &event, 1, NULL, 0, 0)) {
167 close(fd);
168 return false;
169 }
170
171 entry->state = (void *)(size_t)fd;
172 mFdToEntry.emplace(fd, entry);
173 }
174
175 sub.fd = (int)(size_t)entry->state;
176 mSubscriptions.emplace(path, sub);
177 return true;
178}
179
180std::vector<KqueueSubscription *> KqueueBackend::findSubscriptions(std::string &path) {
181 // Find the subscriptions affected by this path.
182 // Copy pointers to them into a vector so that modifying mSubscriptions doesn't invalidate the iterator.
183 auto range = mSubscriptions.equal_range(path);
184 std::vector<KqueueSubscription *> subs;
185 for (auto it = range.first; it != range.second; it++) {
186 subs.push_back(&it->second);
187 }
188
189 return subs;
190}
191
192bool KqueueBackend::compareDir(int fd, std::string &path, std::unordered_set<WatcherRef> &watchers) {
193 // macOS doesn't support fdclosedir, so we have to duplicate the file descriptor
194 // to ensure the closedir doesn't also stop watching.
195 #if __APPLE__
196 fd = dup(fd);
197 #endif
198
199 DIR *dir = fdopendir(fd);
200 if (dir == NULL) {
201 return false;
202 }
203
204 // fdopendir doesn't rewind to the beginning.
205 rewinddir(dir);
206
207 std::vector<KqueueSubscription *> subs = findSubscriptions(path);
208 std::string dirStart = path + DIR_SEP;
209
210 std::unordered_set<std::shared_ptr<DirTree>> trees;
211 for (auto it = subs.begin(); it != subs.end(); it++) {
212 trees.emplace((*it)->tree);
213 }
214
215 std::unordered_set<std::string> entries;
216 struct dirent *entry;
217 while ((entry = readdir(dir))) {
218 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
219 continue;
220 }
221
222 std::string fullpath = dirStart + entry->d_name;
223 entries.emplace(fullpath);
224
225 for (auto it = trees.begin(); it != trees.end(); it++) {
226 std::shared_ptr<DirTree> tree = *it;
227 if (!tree->find(fullpath)) {
228 struct stat st;
229 fstatat(fd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW);
230 tree->add(fullpath, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode));
231
232 // Notify all watchers with the same tree.
233 for (auto i = subs.begin(); i != subs.end(); i++) {
234 KqueueSubscription *sub = *i;
235 if (sub->tree == tree) {
236 if (sub->watcher->isIgnored(fullpath)) {
237 continue;
238 }
239
240 sub->watcher->mEvents.create(fullpath);
241 watchers.emplace(sub->watcher);
242
243 bool success = watchDir(sub->watcher, fullpath, sub->tree);
244 if (!success) {
245 sub->tree->remove(fullpath);
246 return false;
247 }
248 }
249 }
250 }
251 }
252 }
253
254 for (auto it = trees.begin(); it != trees.end(); it++) {
255 std::shared_ptr<DirTree> tree = *it;
256 for (auto entry = tree->entries.begin(); entry != tree->entries.end();) {
257
258 if (
259 entry->first.rfind(dirStart, 0) == 0 &&
260 entry->first.find(DIR_SEP, dirStart.length()) == std::string::npos &&
261 entries.count(entry->first) == 0
262 ) {
263 // Notify all watchers with the same tree.
264 for (auto i = subs.begin(); i != subs.end(); i++) {
265 if ((*i)->tree == tree) {
266 KqueueSubscription *sub = *i;
267 if (!sub->watcher->isIgnored(entry->first)) {
268 sub->watcher->mEvents.remove(entry->first);
269 watchers.emplace(sub->watcher);
270 }
271 }
272 }
273
274 mFdToEntry.erase((int)(size_t)entry->second.state);
275 mSubscriptions.erase(entry->first);
276 entry = tree->entries.erase(entry);
277 } else {
278 entry++;
279 }
280 }
281 }
282
283 #if __APPLE__
284 closedir(dir);
285 #else
286 fdclosedir(dir);
287 #endif
288
289 return true;
290}
291
292void KqueueBackend::unsubscribe(WatcherRef watcher) {
293 // Find any subscriptions pointing to this watcher, and remove them.
294 for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
295 if (it->second.watcher.get() == watcher.get()) {
296 if (mSubscriptions.count(it->first) == 1) {
297 // Closing the file descriptor automatically unwatches it in the kqueue.
298 close(it->second.fd);
299 mFdToEntry.erase(it->second.fd);
300 }
301
302 it = mSubscriptions.erase(it);
303 } else {
304 it++;
305 }
306 }
307}
Note: See TracBrowser for help on using the repository browser.