|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/files/file_path_watcher_kqueue.h" | 
|  |  | 
|  | #include <fcntl.h> | 
|  | #include <stddef.h> | 
|  | #include <sys/param.h> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/threading/sequenced_task_runner_handle.h" | 
|  |  | 
|  | // On some platforms these are not defined. | 
|  | #if !defined(EV_RECEIPT) | 
|  | #define EV_RECEIPT 0 | 
|  | #endif | 
|  | #if !defined(O_EVTONLY) | 
|  | #define O_EVTONLY O_RDONLY | 
|  | #endif | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | FilePathWatcherKQueue::FilePathWatcherKQueue() : kqueue_(-1) {} | 
|  |  | 
|  | FilePathWatcherKQueue::~FilePathWatcherKQueue() { | 
|  | DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence()); | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::ReleaseEvent(struct kevent& event) { | 
|  | CloseFileDescriptor(&event.ident); | 
|  | EventData* entry = EventDataForKevent(event); | 
|  | delete entry; | 
|  | event.udata = NULL; | 
|  | } | 
|  |  | 
|  | int FilePathWatcherKQueue::EventsForPath(FilePath path, EventVector* events) { | 
|  | // Make sure that we are working with a clean slate. | 
|  | DCHECK(events->empty()); | 
|  |  | 
|  | std::vector<FilePath::StringType> components; | 
|  | path.GetComponents(&components); | 
|  |  | 
|  | if (components.size() < 1) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | int last_existing_entry = 0; | 
|  | FilePath built_path; | 
|  | bool path_still_exists = true; | 
|  | for (std::vector<FilePath::StringType>::iterator i = components.begin(); | 
|  | i != components.end(); ++i) { | 
|  | if (i == components.begin()) { | 
|  | built_path = FilePath(*i); | 
|  | } else { | 
|  | built_path = built_path.Append(*i); | 
|  | } | 
|  | uintptr_t fd = kNoFileDescriptor; | 
|  | if (path_still_exists) { | 
|  | fd = FileDescriptorForPath(built_path); | 
|  | if (fd == kNoFileDescriptor) { | 
|  | path_still_exists = false; | 
|  | } else { | 
|  | ++last_existing_entry; | 
|  | } | 
|  | } | 
|  | FilePath::StringType subdir = (i != (components.end() - 1)) ? *(i + 1) : ""; | 
|  | EventData* data = new EventData(built_path, subdir); | 
|  | struct kevent event; | 
|  | EV_SET(&event, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR | EV_RECEIPT), | 
|  | (NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB | | 
|  | NOTE_RENAME | NOTE_REVOKE | NOTE_EXTEND), 0, data); | 
|  | events->push_back(event); | 
|  | } | 
|  | return last_existing_entry; | 
|  | } | 
|  |  | 
|  | uintptr_t FilePathWatcherKQueue::FileDescriptorForPath(const FilePath& path) { | 
|  | int fd = HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY)); | 
|  | if (fd == -1) | 
|  | return kNoFileDescriptor; | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::CloseFileDescriptor(uintptr_t* fd) { | 
|  | if (*fd == kNoFileDescriptor) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (IGNORE_EINTR(close(*fd)) != 0) { | 
|  | DPLOG(ERROR) << "close"; | 
|  | } | 
|  | *fd = kNoFileDescriptor; | 
|  | } | 
|  |  | 
|  | bool FilePathWatcherKQueue::AreKeventValuesValid(struct kevent* kevents, | 
|  | int count) { | 
|  | if (count < 0) { | 
|  | DPLOG(ERROR) << "kevent"; | 
|  | return false; | 
|  | } | 
|  | bool valid = true; | 
|  | for (int i = 0; i < count; ++i) { | 
|  | if (kevents[i].flags & EV_ERROR && kevents[i].data) { | 
|  | // Find the kevent in |events_| that matches the kevent with the error. | 
|  | EventVector::iterator event = events_.begin(); | 
|  | for (; event != events_.end(); ++event) { | 
|  | if (event->ident == kevents[i].ident) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | std::string path_name; | 
|  | if (event != events_.end()) { | 
|  | EventData* event_data = EventDataForKevent(*event); | 
|  | if (event_data != NULL) { | 
|  | path_name = event_data->path_.value(); | 
|  | } | 
|  | } | 
|  | if (path_name.empty()) { | 
|  | path_name = base::StringPrintf( | 
|  | "fd %ld", reinterpret_cast<long>(&kevents[i].ident)); | 
|  | } | 
|  | DLOG(ERROR) << "Error: " << kevents[i].data << " for " << path_name; | 
|  | valid = false; | 
|  | } | 
|  | } | 
|  | return valid; | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::HandleAttributesChange( | 
|  | const EventVector::iterator& event, | 
|  | bool* target_file_affected, | 
|  | bool* update_watches) { | 
|  | EventVector::iterator next_event = event + 1; | 
|  | EventData* next_event_data = EventDataForKevent(*next_event); | 
|  | // Check to see if the next item in path is still accessible. | 
|  | uintptr_t have_access = FileDescriptorForPath(next_event_data->path_); | 
|  | if (have_access == kNoFileDescriptor) { | 
|  | *target_file_affected = true; | 
|  | *update_watches = true; | 
|  | EventVector::iterator local_event(event); | 
|  | for (; local_event != events_.end(); ++local_event) { | 
|  | // Close all nodes from the event down. This has the side effect of | 
|  | // potentially rendering other events in |updates| invalid. | 
|  | // There is no need to remove the events from |kqueue_| because this | 
|  | // happens as a side effect of closing the file descriptor. | 
|  | CloseFileDescriptor(&local_event->ident); | 
|  | } | 
|  | } else { | 
|  | CloseFileDescriptor(&have_access); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::HandleDeleteOrMoveChange( | 
|  | const EventVector::iterator& event, | 
|  | bool* target_file_affected, | 
|  | bool* update_watches) { | 
|  | *target_file_affected = true; | 
|  | *update_watches = true; | 
|  | EventVector::iterator local_event(event); | 
|  | for (; local_event != events_.end(); ++local_event) { | 
|  | // Close all nodes from the event down. This has the side effect of | 
|  | // potentially rendering other events in |updates| invalid. | 
|  | // There is no need to remove the events from |kqueue_| because this | 
|  | // happens as a side effect of closing the file descriptor. | 
|  | CloseFileDescriptor(&local_event->ident); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::HandleCreateItemChange( | 
|  | const EventVector::iterator& event, | 
|  | bool* target_file_affected, | 
|  | bool* update_watches) { | 
|  | // Get the next item in the path. | 
|  | EventVector::iterator next_event = event + 1; | 
|  | // Check to see if it already has a valid file descriptor. | 
|  | if (!IsKeventFileDescriptorOpen(*next_event)) { | 
|  | EventData* next_event_data = EventDataForKevent(*next_event); | 
|  | // If not, attempt to open a file descriptor for it. | 
|  | next_event->ident = FileDescriptorForPath(next_event_data->path_); | 
|  | if (IsKeventFileDescriptorOpen(*next_event)) { | 
|  | *update_watches = true; | 
|  | if (next_event_data->subdir_.empty()) { | 
|  | *target_file_affected = true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool FilePathWatcherKQueue::UpdateWatches(bool* target_file_affected) { | 
|  | // Iterate over events adding kevents for items that exist to the kqueue. | 
|  | // Then check to see if new components in the path have been created. | 
|  | // Repeat until no new components in the path are detected. | 
|  | // This is to get around races in directory creation in a watched path. | 
|  | bool update_watches = true; | 
|  | while (update_watches) { | 
|  | size_t valid; | 
|  | for (valid = 0; valid < events_.size(); ++valid) { | 
|  | if (!IsKeventFileDescriptorOpen(events_[valid])) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (valid == 0) { | 
|  | // The root of the file path is inaccessible? | 
|  | return false; | 
|  | } | 
|  |  | 
|  | EventVector updates(valid); | 
|  | int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], valid, &updates[0], | 
|  | valid, NULL)); | 
|  | if (!AreKeventValuesValid(&updates[0], count)) { | 
|  | return false; | 
|  | } | 
|  | update_watches = false; | 
|  | for (; valid < events_.size(); ++valid) { | 
|  | EventData* event_data = EventDataForKevent(events_[valid]); | 
|  | events_[valid].ident = FileDescriptorForPath(event_data->path_); | 
|  | if (IsKeventFileDescriptorOpen(events_[valid])) { | 
|  | update_watches = true; | 
|  | if (event_data->subdir_.empty()) { | 
|  | *target_file_affected = true; | 
|  | } | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FilePathWatcherKQueue::Watch(const FilePath& path, | 
|  | bool recursive, | 
|  | const FilePathWatcher::Callback& callback) { | 
|  | DCHECK(target_.value().empty());  // Can only watch one path. | 
|  | DCHECK(!callback.is_null()); | 
|  | DCHECK_EQ(kqueue_, -1); | 
|  | // Recursive watch is not supported using kqueue. | 
|  | DCHECK(!recursive); | 
|  |  | 
|  | callback_ = callback; | 
|  | target_ = path; | 
|  |  | 
|  | set_task_runner(SequencedTaskRunnerHandle::Get()); | 
|  |  | 
|  | kqueue_ = kqueue(); | 
|  | if (kqueue_ == -1) { | 
|  | DPLOG(ERROR) << "kqueue"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int last_entry = EventsForPath(target_, &events_); | 
|  | DCHECK_NE(last_entry, 0); | 
|  |  | 
|  | EventVector responses(last_entry); | 
|  |  | 
|  | int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], last_entry, | 
|  | &responses[0], last_entry, NULL)); | 
|  | if (!AreKeventValuesValid(&responses[0], count)) { | 
|  | // Calling Cancel() here to close any file descriptors that were opened. | 
|  | // This would happen in the destructor anyways, but FilePathWatchers tend to | 
|  | // be long lived, and if an error has occurred, there is no reason to waste | 
|  | // the file descriptors. | 
|  | Cancel(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // It's safe to use Unretained() because the watch is cancelled and the | 
|  | // callback cannot be invoked after |kqueue_watch_controller_| (which is a | 
|  | // member of |this|) has been deleted. | 
|  | kqueue_watch_controller_ = FileDescriptorWatcher::WatchReadable( | 
|  | kqueue_, | 
|  | Bind(&FilePathWatcherKQueue::OnKQueueReadable, Unretained(this))); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::Cancel() { | 
|  | if (!task_runner()) { | 
|  | set_cancelled(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK(task_runner()->RunsTasksInCurrentSequence()); | 
|  | if (!is_cancelled()) { | 
|  | set_cancelled(); | 
|  | kqueue_watch_controller_.reset(); | 
|  | if (IGNORE_EINTR(close(kqueue_)) != 0) { | 
|  | DPLOG(ERROR) << "close kqueue"; | 
|  | } | 
|  | kqueue_ = -1; | 
|  | std::for_each(events_.begin(), events_.end(), ReleaseEvent); | 
|  | events_.clear(); | 
|  | callback_.Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FilePathWatcherKQueue::OnKQueueReadable() { | 
|  | DCHECK(task_runner()->RunsTasksInCurrentSequence()); | 
|  | DCHECK(events_.size()); | 
|  |  | 
|  | // Request the file system update notifications that have occurred and return | 
|  | // them in |updates|. |count| will contain the number of updates that have | 
|  | // occurred. | 
|  | EventVector updates(events_.size()); | 
|  | struct timespec timeout = {0, 0}; | 
|  | int count = HANDLE_EINTR(kevent(kqueue_, NULL, 0, &updates[0], updates.size(), | 
|  | &timeout)); | 
|  |  | 
|  | // Error values are stored within updates, so check to make sure that no | 
|  | // errors occurred. | 
|  | if (!AreKeventValuesValid(&updates[0], count)) { | 
|  | callback_.Run(target_, true /* error */); | 
|  | Cancel(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | bool update_watches = false; | 
|  | bool send_notification = false; | 
|  |  | 
|  | // Iterate through each of the updates and react to them. | 
|  | for (int i = 0; i < count; ++i) { | 
|  | // Find our kevent record that matches the update notification. | 
|  | EventVector::iterator event = events_.begin(); | 
|  | for (; event != events_.end(); ++event) { | 
|  | if (!IsKeventFileDescriptorOpen(*event) || | 
|  | event->ident == updates[i].ident) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (event == events_.end() || !IsKeventFileDescriptorOpen(*event)) { | 
|  | // The event may no longer exist in |events_| because another event | 
|  | // modified |events_| in such a way to make it invalid. For example if | 
|  | // the path is /foo/bar/bam and foo is deleted, NOTE_DELETE events for | 
|  | // foo, bar and bam will be sent. If foo is processed first, then | 
|  | // the file descriptors for bar and bam will already be closed and set | 
|  | // to -1 before they get a chance to be processed. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | EventData* event_data = EventDataForKevent(*event); | 
|  |  | 
|  | // If the subdir is empty, this is the last item on the path and is the | 
|  | // target file. | 
|  | bool target_file_affected = event_data->subdir_.empty(); | 
|  | if ((updates[i].fflags & NOTE_ATTRIB) && !target_file_affected) { | 
|  | HandleAttributesChange(event, &target_file_affected, &update_watches); | 
|  | } | 
|  | if (updates[i].fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) { | 
|  | HandleDeleteOrMoveChange(event, &target_file_affected, &update_watches); | 
|  | } | 
|  | if ((updates[i].fflags & NOTE_WRITE) && !target_file_affected) { | 
|  | HandleCreateItemChange(event, &target_file_affected, &update_watches); | 
|  | } | 
|  | send_notification |= target_file_affected; | 
|  | } | 
|  |  | 
|  | if (update_watches) { | 
|  | if (!UpdateWatches(&send_notification)) { | 
|  | callback_.Run(target_, true /* error */); | 
|  | Cancel(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (send_notification) { | 
|  | callback_.Run(target_, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace base |