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