source: imaps-frontend/node_modules/@parcel/watcher/src/linux/InotifyBackend.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: 6.7 KB
Line 
1#include <memory>
2#include <poll.h>
3#include <unistd.h>
4#include <fcntl.h>
5#include <sys/stat.h>
6#include "InotifyBackend.hh"
7
8#define INOTIFY_MASK \
9 IN_ATTRIB | IN_CREATE | IN_DELETE | \
10 IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | \
11 IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR | IN_EXCL_UNLINK
12#define BUFFER_SIZE 8192
13#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
14
15void InotifyBackend::start() {
16 // Create a pipe that we will write to when we want to end the thread.
17 int err = pipe2(mPipe, O_CLOEXEC | O_NONBLOCK);
18 if (err == -1) {
19 throw std::runtime_error(std::string("Unable to open pipe: ") + strerror(errno));
20 }
21
22 // Init inotify file descriptor.
23 mInotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
24 if (mInotify == -1) {
25 throw std::runtime_error(std::string("Unable to initialize inotify: ") + strerror(errno));
26 }
27
28 pollfd pollfds[2];
29 pollfds[0].fd = mPipe[0];
30 pollfds[0].events = POLLIN;
31 pollfds[0].revents = 0;
32 pollfds[1].fd = mInotify;
33 pollfds[1].events = POLLIN;
34 pollfds[1].revents = 0;
35
36 notifyStarted();
37
38 // Loop until we get an event from the pipe.
39 while (true) {
40 int result = poll(pollfds, 2, 500);
41 if (result < 0) {
42 throw std::runtime_error(std::string("Unable to poll: ") + strerror(errno));
43 }
44
45 if (pollfds[0].revents) {
46 break;
47 }
48
49 if (pollfds[1].revents) {
50 handleEvents();
51 }
52 }
53
54 close(mPipe[0]);
55 close(mPipe[1]);
56 close(mInotify);
57
58 mEndedSignal.notify();
59}
60
61InotifyBackend::~InotifyBackend() {
62 write(mPipe[1], "X", 1);
63 mEndedSignal.wait();
64}
65
66// This function is called by Backend::watch which takes a lock on mMutex
67void InotifyBackend::subscribe(WatcherRef watcher) {
68 // Build a full directory tree recursively, and watch each directory.
69 std::shared_ptr<DirTree> tree = getTree(watcher);
70
71 for (auto it = tree->entries.begin(); it != tree->entries.end(); it++) {
72 if (it->second.isDir) {
73 bool success = watchDir(watcher, it->second.path, tree);
74 if (!success) {
75 throw WatcherError(std::string("inotify_add_watch on '") + it->second.path + std::string("' failed: ") + strerror(errno), watcher);
76 }
77 }
78 }
79}
80
81bool InotifyBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree) {
82 int wd = inotify_add_watch(mInotify, path.c_str(), INOTIFY_MASK);
83 if (wd == -1) {
84 return false;
85 }
86
87 std::shared_ptr<InotifySubscription> sub = std::make_shared<InotifySubscription>();
88 sub->tree = tree;
89 sub->path = path;
90 sub->watcher = watcher;
91 mSubscriptions.emplace(wd, sub);
92
93 return true;
94}
95
96void InotifyBackend::handleEvents() {
97 char buf[BUFFER_SIZE] __attribute__ ((aligned(__alignof__(struct inotify_event))));;
98 struct inotify_event *event;
99
100 // Track all of the watchers that are touched so we can notify them at the end of the events.
101 std::unordered_set<WatcherRef> watchers;
102
103 while (true) {
104 int n = read(mInotify, &buf, BUFFER_SIZE);
105 if (n < 0) {
106 if (errno == EAGAIN || errno == EWOULDBLOCK) {
107 break;
108 }
109
110 throw std::runtime_error(std::string("Error reading from inotify: ") + strerror(errno));
111 }
112
113 if (n == 0) {
114 break;
115 }
116
117 for (char *ptr = buf; ptr < buf + n; ptr += sizeof(*event) + event->len) {
118 event = (struct inotify_event *)ptr;
119
120 if ((event->mask & IN_Q_OVERFLOW) == IN_Q_OVERFLOW) {
121 // overflow
122 continue;
123 }
124
125 handleEvent(event, watchers);
126 }
127 }
128
129 for (auto it = watchers.begin(); it != watchers.end(); it++) {
130 (*it)->notify();
131 }
132}
133
134void InotifyBackend::handleEvent(struct inotify_event *event, std::unordered_set<WatcherRef> &watchers) {
135 std::unique_lock<std::mutex> lock(mMutex);
136
137 // Find the subscriptions for this watch descriptor
138 auto range = mSubscriptions.equal_range(event->wd);
139 std::unordered_set<std::shared_ptr<InotifySubscription>> set;
140 for (auto it = range.first; it != range.second; it++) {
141 set.insert(it->second);
142 }
143
144 for (auto it = set.begin(); it != set.end(); it++) {
145 if (handleSubscription(event, *it)) {
146 watchers.insert((*it)->watcher);
147 }
148 }
149}
150
151bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared_ptr<InotifySubscription> sub) {
152 // Build full path and check if its in our ignore list.
153 std::shared_ptr<Watcher> watcher = sub->watcher;
154 std::string path = std::string(sub->path);
155 bool isDir = event->mask & IN_ISDIR;
156
157 if (event->len > 0) {
158 path += "/" + std::string(event->name);
159 }
160
161 if (watcher->isIgnored(path)) {
162 return false;
163 }
164
165 // If this is a create, check if it's a directory and start watching if it is.
166 // In any case, keep the directory tree up to date.
167 if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
168 watcher->mEvents.create(path);
169
170 struct stat st;
171 // Use lstat to avoid resolving symbolic links that we cannot watch anyway
172 // https://github.com/parcel-bundler/watcher/issues/76
173 lstat(path.c_str(), &st);
174 DirEntry *entry = sub->tree->add(path, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode));
175
176 if (entry->isDir) {
177 bool success = watchDir(watcher, path, sub->tree);
178 if (!success) {
179 sub->tree->remove(path);
180 return false;
181 }
182 }
183 } else if (event->mask & (IN_MODIFY | IN_ATTRIB)) {
184 watcher->mEvents.update(path);
185
186 struct stat st;
187 stat(path.c_str(), &st);
188 sub->tree->update(path, CONVERT_TIME(st.st_mtim));
189 } else if (event->mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF)) {
190 bool isSelfEvent = (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF));
191 // Ignore delete/move self events unless this is the recursive watch root
192 if (isSelfEvent && path != watcher->mDir) {
193 return false;
194 }
195
196 // If the entry being deleted/moved is a directory, remove it from the list of subscriptions
197 // XXX: self events don't have the IN_ISDIR mask
198 if (isSelfEvent || isDir) {
199 for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
200 if (it->second->path == path) {
201 it = mSubscriptions.erase(it);
202 } else {
203 ++it;
204 }
205 }
206 }
207
208 watcher->mEvents.remove(path);
209 sub->tree->remove(path);
210 }
211
212 return true;
213}
214
215// This function is called by Backend::unwatch which takes a lock on mMutex
216void InotifyBackend::unsubscribe(WatcherRef watcher) {
217 // Find any subscriptions pointing to this watcher, and remove them.
218 for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
219 if (it->second->watcher.get() == watcher.get()) {
220 if (mSubscriptions.count(it->first) == 1) {
221 int err = inotify_rm_watch(mInotify, it->first);
222 if (err == -1) {
223 throw WatcherError(std::string("Unable to remove watcher: ") + strerror(errno), watcher);
224 }
225 }
226
227 it = mSubscriptions.erase(it);
228 } else {
229 it++;
230 }
231 }
232}
Note: See TracBrowser for help on using the repository browser.