1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "base/files/file_path_watcher_kqueue.h"
11
12 #include <fcntl.h>
13 #include <stddef.h>
14 #include <sys/param.h>
15
16 #include <string>
17 #include <vector>
18
19 #include "base/file_descriptor_posix.h"
20 #include "base/files/file_util.h"
21 #include "base/functional/bind.h"
22 #include "base/logging.h"
23 #include "base/ranges/algorithm.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/task/sequenced_task_runner.h"
26 #include "base/threading/scoped_blocking_call.h"
27
28 // On some platforms these are not defined.
29 #if !defined(EV_RECEIPT)
30 #define EV_RECEIPT 0
31 #endif
32 #if !defined(O_EVTONLY)
33 #define O_EVTONLY O_RDONLY
34 #endif
35
36 namespace base {
37
FilePathWatcherKQueue()38 FilePathWatcherKQueue::FilePathWatcherKQueue() : kqueue_(-1) {}
39
~FilePathWatcherKQueue()40 FilePathWatcherKQueue::~FilePathWatcherKQueue() {
41 DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
42 }
43
ReleaseEvent(struct kevent & event)44 void FilePathWatcherKQueue::ReleaseEvent(struct kevent& event) {
45 CloseFileDescriptor(&event.ident);
46 EventData* entry = EventDataForKevent(event);
47 delete entry;
48 event.udata = NULL;
49 }
50
EventsForPath(FilePath path,EventVector * events)51 size_t FilePathWatcherKQueue::EventsForPath(FilePath path,
52 EventVector* events) {
53 // Make sure that we are working with a clean slate.
54 DCHECK(events->empty());
55
56 std::vector<FilePath::StringType> components = path.GetComponents();
57
58 if (components.empty()) {
59 return 0;
60 }
61
62 size_t last_existing_entry = 0;
63 FilePath built_path;
64 bool path_still_exists = true;
65 for (std::vector<FilePath::StringType>::iterator i = components.begin();
66 i != components.end(); ++i) {
67 if (i == components.begin()) {
68 built_path = FilePath(*i);
69 } else {
70 built_path = built_path.Append(*i);
71 }
72 uintptr_t fd = kNoFileDescriptor;
73 if (path_still_exists) {
74 fd = FileDescriptorForPath(built_path);
75 if (fd == kNoFileDescriptor) {
76 path_still_exists = false;
77 } else {
78 ++last_existing_entry;
79 }
80 }
81 FilePath::StringType subdir = (i != (components.end() - 1)) ? *(i + 1) : "";
82 EventData* data = new EventData(built_path, subdir);
83 struct kevent event;
84 EV_SET(&event, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR | EV_RECEIPT),
85 (NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB |
86 NOTE_RENAME | NOTE_REVOKE | NOTE_EXTEND), 0, data);
87 events->push_back(event);
88 }
89 return last_existing_entry;
90 }
91
92 // static
EventForItem(const FilePath & path,EventVector * events)93 size_t FilePathWatcherKQueue::EventForItem(const FilePath& path,
94 EventVector* events) {
95 // Make sure that we are working with a clean slate.
96 DCHECK(events->empty());
97
98 events->resize(1);
99 auto& event = events->front();
100 EV_SET(&event, FileDescriptorForPath(path), EVFILT_VNODE,
101 (EV_ADD | EV_CLEAR | EV_RECEIPT),
102 (NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE |
103 NOTE_EXTEND),
104 0, new EventData(path, /*subdir=*/FilePath::StringType()));
105
106 return event.ident != kNoFileDescriptor ? 1 : 0;
107 }
108
FileDescriptorForPath(const FilePath & path)109 uintptr_t FilePathWatcherKQueue::FileDescriptorForPath(const FilePath& path) {
110 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
111 int fd = HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY));
112 if (fd < 0)
113 return kNoFileDescriptor;
114 return static_cast<uintptr_t>(fd);
115 }
116
CloseFileDescriptor(uintptr_t * fd)117 void FilePathWatcherKQueue::CloseFileDescriptor(uintptr_t* fd) {
118 if (*fd == kNoFileDescriptor) {
119 return;
120 }
121
122 if (IGNORE_EINTR(close(checked_cast<int>(*fd))) != 0) {
123 DPLOG(ERROR) << "close";
124 }
125 *fd = kNoFileDescriptor;
126 }
127
AreKeventValuesValid(struct kevent * kevents,int count)128 bool FilePathWatcherKQueue::AreKeventValuesValid(struct kevent* kevents,
129 int count) {
130 if (count < 0) {
131 DPLOG(ERROR) << "kevent";
132 return false;
133 }
134 bool valid = true;
135 for (int i = 0; i < count; ++i) {
136 if (kevents[i].flags & EV_ERROR && kevents[i].data) {
137 // Find the kevent in |events_| that matches the kevent with the error.
138 EventVector::iterator event = events_.begin();
139 for (; event != events_.end(); ++event) {
140 if (event->ident == kevents[i].ident) {
141 break;
142 }
143 }
144 std::string path_name;
145 if (event != events_.end()) {
146 EventData* event_data = EventDataForKevent(*event);
147 if (event_data != NULL) {
148 path_name = event_data->path_.value();
149 }
150 }
151 if (path_name.empty()) {
152 path_name = base::StringPrintf(
153 "fd %ld", reinterpret_cast<long>(&kevents[i].ident));
154 }
155 DLOG(ERROR) << "Error: " << kevents[i].data << " for " << path_name;
156 valid = false;
157 }
158 }
159 return valid;
160 }
161
HandleAttributesChange(const EventVector::iterator & event,bool * target_file_affected,bool * update_watches)162 void FilePathWatcherKQueue::HandleAttributesChange(
163 const EventVector::iterator& event,
164 bool* target_file_affected,
165 bool* update_watches) {
166 EventVector::iterator next_event = event + 1;
167 EventData* next_event_data = EventDataForKevent(*next_event);
168 // Check to see if the next item in path is still accessible.
169 uintptr_t have_access = FileDescriptorForPath(next_event_data->path_);
170 if (have_access == kNoFileDescriptor) {
171 *target_file_affected = true;
172 *update_watches = true;
173 EventVector::iterator local_event(event);
174 for (; local_event != events_.end(); ++local_event) {
175 // Close all nodes from the event down. This has the side effect of
176 // potentially rendering other events in |updates| invalid.
177 // There is no need to remove the events from |kqueue_| because this
178 // happens as a side effect of closing the file descriptor.
179 CloseFileDescriptor(&local_event->ident);
180 }
181 } else {
182 CloseFileDescriptor(&have_access);
183 }
184 }
185
HandleDeleteOrMoveChange(const EventVector::iterator & event,bool * target_file_affected,bool * update_watches)186 void FilePathWatcherKQueue::HandleDeleteOrMoveChange(
187 const EventVector::iterator& event,
188 bool* target_file_affected,
189 bool* update_watches) {
190 *target_file_affected = true;
191 *update_watches = true;
192 EventVector::iterator local_event(event);
193 for (; local_event != events_.end(); ++local_event) {
194 // Close all nodes from the event down. This has the side effect of
195 // potentially rendering other events in |updates| invalid.
196 // There is no need to remove the events from |kqueue_| because this
197 // happens as a side effect of closing the file descriptor.
198 CloseFileDescriptor(&local_event->ident);
199 }
200 }
201
HandleCreateItemChange(const EventVector::iterator & event,bool * target_file_affected,bool * update_watches)202 void FilePathWatcherKQueue::HandleCreateItemChange(
203 const EventVector::iterator& event,
204 bool* target_file_affected,
205 bool* update_watches) {
206 // Get the next item in the path.
207 EventVector::iterator next_event = event + 1;
208 // Check to see if it already has a valid file descriptor.
209 if (!IsKeventFileDescriptorOpen(*next_event)) {
210 EventData* next_event_data = EventDataForKevent(*next_event);
211 // If not, attempt to open a file descriptor for it.
212 next_event->ident = FileDescriptorForPath(next_event_data->path_);
213 if (IsKeventFileDescriptorOpen(*next_event)) {
214 *update_watches = true;
215 if (next_event_data->subdir_.empty()) {
216 *target_file_affected = true;
217 }
218 }
219 }
220 }
221
UpdateWatches(bool * target_file_affected)222 bool FilePathWatcherKQueue::UpdateWatches(bool* target_file_affected) {
223 // Iterate over events adding kevents for items that exist to the kqueue.
224 // Then check to see if new components in the path have been created.
225 // Repeat until no new components in the path are detected.
226 // This is to get around races in directory creation in a watched path.
227 bool update_watches = true;
228 while (update_watches) {
229 size_t valid;
230 for (valid = 0; valid < events_.size(); ++valid) {
231 if (!IsKeventFileDescriptorOpen(events_[valid])) {
232 break;
233 }
234 }
235 if (valid == 0) {
236 // The root of the file path is inaccessible?
237 return false;
238 }
239
240 EventVector updates(valid);
241 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
242 const int valid_int = checked_cast<int>(valid);
243 int count = HANDLE_EINTR(
244 kevent(kqueue_, &events_[0], valid_int, &updates[0], valid_int, NULL));
245 if (!AreKeventValuesValid(&updates[0], count)) {
246 return false;
247 }
248 update_watches = false;
249 for (; valid < events_.size(); ++valid) {
250 EventData* event_data = EventDataForKevent(events_[valid]);
251 events_[valid].ident = FileDescriptorForPath(event_data->path_);
252 if (IsKeventFileDescriptorOpen(events_[valid])) {
253 update_watches = true;
254 if (event_data->subdir_.empty()) {
255 *target_file_affected = true;
256 }
257 } else {
258 break;
259 }
260 }
261 }
262 return true;
263 }
264
Watch(const FilePath & path,Type type,const FilePathWatcher::Callback & callback)265 bool FilePathWatcherKQueue::Watch(const FilePath& path,
266 Type type,
267 const FilePathWatcher::Callback& callback) {
268 DCHECK(target_.value().empty()); // Can only watch one path.
269 DCHECK(!callback.is_null());
270 DCHECK_EQ(kqueue_, -1);
271 // Recursive watch is not supported using kqueue.
272 DCHECK_NE(type, Type::kRecursive);
273
274 callback_ = callback;
275 target_ = path;
276
277 set_task_runner(SequencedTaskRunner::GetCurrentDefault());
278
279 kqueue_ = kqueue();
280 if (kqueue_ == -1) {
281 DPLOG(ERROR) << "kqueue";
282 return false;
283 }
284
285 size_t last_entry = type == Type::kNonRecursive
286 ? EventsForPath(target_, &events_)
287 : EventForItem(target_, &events_);
288 if (!last_entry) {
289 // No notifications can possibly come in, so fail fast.
290 Cancel();
291 return false;
292 }
293
294 EventVector responses(last_entry);
295
296 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
297 const int last_entry_int = checked_cast<int>(last_entry);
298 int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], last_entry_int,
299 &responses[0], last_entry_int, NULL));
300 if (!AreKeventValuesValid(&responses[0], count)) {
301 // Calling Cancel() here to close any file descriptors that were opened.
302 // This would happen in the destructor anyways, but FilePathWatchers tend to
303 // be long lived, and if an error has occurred, there is no reason to waste
304 // the file descriptors.
305 Cancel();
306 return false;
307 }
308
309 // It's safe to use Unretained() because the watch is cancelled and the
310 // callback cannot be invoked after |kqueue_watch_controller_| (which is a
311 // member of |this|) has been deleted.
312 kqueue_watch_controller_ = FileDescriptorWatcher::WatchReadable(
313 kqueue_, BindRepeating(&FilePathWatcherKQueue::OnKQueueReadable,
314 Unretained(this)));
315
316 return true;
317 }
318
Cancel()319 void FilePathWatcherKQueue::Cancel() {
320 if (!task_runner()) {
321 set_cancelled();
322 return;
323 }
324
325 DCHECK(task_runner()->RunsTasksInCurrentSequence());
326 if (!is_cancelled()) {
327 set_cancelled();
328 kqueue_watch_controller_.reset();
329 if (IGNORE_EINTR(close(kqueue_)) != 0) {
330 DPLOG(ERROR) << "close kqueue";
331 }
332 kqueue_ = -1;
333 base::ranges::for_each(events_, ReleaseEvent);
334 events_.clear();
335 callback_.Reset();
336 }
337 }
338
OnKQueueReadable()339 void FilePathWatcherKQueue::OnKQueueReadable() {
340 DCHECK(task_runner()->RunsTasksInCurrentSequence());
341 DCHECK(events_.size());
342
343 // Request the file system update notifications that have occurred and return
344 // them in |updates|. |count| will contain the number of updates that have
345 // occurred.
346 EventVector updates(events_.size());
347 struct timespec timeout = {0, 0};
348 int count = HANDLE_EINTR(kevent(kqueue_, NULL, 0, &updates[0],
349 checked_cast<int>(updates.size()), &timeout));
350
351 // Error values are stored within updates, so check to make sure that no
352 // errors occurred.
353 if (!AreKeventValuesValid(&updates[0], count)) {
354 callback_.Run(target_, true /* error */);
355 Cancel();
356 return;
357 }
358
359 bool update_watches = false;
360 bool send_notification = false;
361
362 // Iterate through each of the updates and react to them.
363 // AreKeventValuesValid() guarantees `count` is non-negative.
364 for (size_t i = 0; i < static_cast<size_t>(count); ++i) {
365 // Find our kevent record that matches the update notification.
366 EventVector::iterator event = events_.begin();
367 for (; event != events_.end(); ++event) {
368 if (!IsKeventFileDescriptorOpen(*event) ||
369 event->ident == updates[i].ident) {
370 break;
371 }
372 }
373 if (event == events_.end() || !IsKeventFileDescriptorOpen(*event)) {
374 // The event may no longer exist in |events_| because another event
375 // modified |events_| in such a way to make it invalid. For example if
376 // the path is /foo/bar/bam and foo is deleted, NOTE_DELETE events for
377 // foo, bar and bam will be sent. If foo is processed first, then
378 // the file descriptors for bar and bam will already be closed and set
379 // to -1 before they get a chance to be processed.
380 continue;
381 }
382
383 EventData* event_data = EventDataForKevent(*event);
384
385 // If the subdir is empty, this is the last item on the path and is the
386 // target file.
387 bool target_file_affected = event_data->subdir_.empty();
388 if ((updates[i].fflags & NOTE_ATTRIB) && !target_file_affected) {
389 HandleAttributesChange(event, &target_file_affected, &update_watches);
390 }
391 if (updates[i].fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) {
392 HandleDeleteOrMoveChange(event, &target_file_affected, &update_watches);
393 }
394 if ((updates[i].fflags & NOTE_WRITE) && !target_file_affected) {
395 HandleCreateItemChange(event, &target_file_affected, &update_watches);
396 }
397 send_notification |= target_file_affected;
398 }
399
400 if (update_watches) {
401 if (!UpdateWatches(&send_notification)) {
402 callback_.Run(target_, true /* error */);
403 Cancel();
404 return;
405 }
406 }
407
408 if (send_notification) {
409 callback_.Run(target_, false);
410 }
411 }
412
413 } // namespace base
414