| // Copyright 2014 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_fsevents.h" | 
 |  | 
 | #include <dispatch/dispatch.h> | 
 |  | 
 | #include <list> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/lazy_instance.h" | 
 | #include "base/logging.h" | 
 | #include "base/mac/scoped_cftyperef.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/threading/sequenced_task_runner_handle.h" | 
 |  | 
 | namespace base { | 
 |  | 
 | namespace { | 
 |  | 
 | // The latency parameter passed to FSEventsStreamCreate(). | 
 | const CFAbsoluteTime kEventLatencySeconds = 0.3; | 
 |  | 
 | // Resolve any symlinks in the path. | 
 | FilePath ResolvePath(const FilePath& path) { | 
 |   const unsigned kMaxLinksToResolve = 255; | 
 |  | 
 |   std::vector<FilePath::StringType> component_vector; | 
 |   path.GetComponents(&component_vector); | 
 |   std::list<FilePath::StringType> | 
 |       components(component_vector.begin(), component_vector.end()); | 
 |  | 
 |   FilePath result; | 
 |   unsigned resolve_count = 0; | 
 |   while (resolve_count < kMaxLinksToResolve && !components.empty()) { | 
 |     FilePath component(*components.begin()); | 
 |     components.pop_front(); | 
 |  | 
 |     FilePath current; | 
 |     if (component.IsAbsolute()) { | 
 |       current = component; | 
 |     } else { | 
 |       current = result.Append(component); | 
 |     } | 
 |  | 
 |     FilePath target; | 
 |     if (ReadSymbolicLink(current, &target)) { | 
 |       if (target.IsAbsolute()) | 
 |         result.clear(); | 
 |       std::vector<FilePath::StringType> target_components; | 
 |       target.GetComponents(&target_components); | 
 |       components.insert(components.begin(), target_components.begin(), | 
 |                         target_components.end()); | 
 |       resolve_count++; | 
 |     } else { | 
 |       result = current; | 
 |     } | 
 |   } | 
 |  | 
 |   if (resolve_count >= kMaxLinksToResolve) | 
 |     result.clear(); | 
 |   return result; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | FilePathWatcherFSEvents::FilePathWatcherFSEvents() | 
 |     : queue_(dispatch_queue_create( | 
 |           base::StringPrintf("org.chromium.base.FilePathWatcher.%p", this) | 
 |               .c_str(), | 
 |           DISPATCH_QUEUE_SERIAL)), | 
 |       fsevent_stream_(nullptr), | 
 |       weak_factory_(this) {} | 
 |  | 
 | FilePathWatcherFSEvents::~FilePathWatcherFSEvents() { | 
 |   DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence()); | 
 |   DCHECK(callback_.is_null()) | 
 |       << "Cancel() must be called before FilePathWatcher is destroyed."; | 
 | } | 
 |  | 
 | bool FilePathWatcherFSEvents::Watch(const FilePath& path, | 
 |                                     bool recursive, | 
 |                                     const FilePathWatcher::Callback& callback) { | 
 |   DCHECK(!callback.is_null()); | 
 |   DCHECK(callback_.is_null()); | 
 |  | 
 |   // This class could support non-recursive watches, but that is currently | 
 |   // left to FilePathWatcherKQueue. | 
 |   if (!recursive) | 
 |     return false; | 
 |  | 
 |   set_task_runner(SequencedTaskRunnerHandle::Get()); | 
 |   callback_ = callback; | 
 |  | 
 |   FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); | 
 |   // The block runtime would implicitly capture the reference, not the object | 
 |   // it's referencing. Copy the path into a local, so that the value is | 
 |   // captured by the block's scope. | 
 |   const FilePath path_copy(path); | 
 |  | 
 |   dispatch_async(queue_, ^{ | 
 |       StartEventStream(start_event, path_copy); | 
 |   }); | 
 |   return true; | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::Cancel() { | 
 |   set_cancelled(); | 
 |   callback_.Reset(); | 
 |  | 
 |   // Switch to the dispatch queue to tear down the event stream. As the queue is | 
 |   // owned by |this|, and this method is called from the destructor, execute the | 
 |   // block synchronously. | 
 |   dispatch_sync(queue_, ^{ | 
 |     if (fsevent_stream_) { | 
 |       DestroyEventStream(); | 
 |       target_.clear(); | 
 |       resolved_target_.clear(); | 
 |     } | 
 |   }); | 
 | } | 
 |  | 
 | // static | 
 | void FilePathWatcherFSEvents::FSEventsCallback( | 
 |     ConstFSEventStreamRef stream, | 
 |     void* event_watcher, | 
 |     size_t num_events, | 
 |     void* event_paths, | 
 |     const FSEventStreamEventFlags flags[], | 
 |     const FSEventStreamEventId event_ids[]) { | 
 |   FilePathWatcherFSEvents* watcher = | 
 |       reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher); | 
 |   bool root_changed = watcher->ResolveTargetPath(); | 
 |   std::vector<FilePath> paths; | 
 |   FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream); | 
 |   for (size_t i = 0; i < num_events; i++) { | 
 |     if (flags[i] & kFSEventStreamEventFlagRootChanged) | 
 |       root_changed = true; | 
 |     if (event_ids[i]) | 
 |       root_change_at = std::min(root_change_at, event_ids[i]); | 
 |     paths.push_back(FilePath( | 
 |         reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators()); | 
 |   } | 
 |  | 
 |   // Reinitialize the event stream if we find changes to the root. This is | 
 |   // necessary since FSEvents doesn't report any events for the subtree after | 
 |   // the directory to be watched gets created. | 
 |   if (root_changed) { | 
 |     // Resetting the event stream from within the callback fails (FSEvents spews | 
 |     // bad file descriptor errors), so do the reset asynchronously. | 
 |     // | 
 |     // We can't dispatch_async a call to UpdateEventStream() directly because | 
 |     // there would be no guarantee that |watcher| still exists when it runs. | 
 |     // | 
 |     // Instead, bounce on task_runner() and use a WeakPtr to verify that | 
 |     // |watcher| still exists. If it does, dispatch_async a call to | 
 |     // UpdateEventStream(). Because the destructor of |watcher| runs on | 
 |     // task_runner() and calls dispatch_sync, it is guaranteed that |watcher| | 
 |     // still exists when UpdateEventStream() runs. | 
 |     watcher->task_runner()->PostTask( | 
 |         FROM_HERE, Bind( | 
 |                        [](WeakPtr<FilePathWatcherFSEvents> weak_watcher, | 
 |                           FSEventStreamEventId root_change_at) { | 
 |                          if (!weak_watcher) | 
 |                            return; | 
 |                          FilePathWatcherFSEvents* watcher = weak_watcher.get(); | 
 |                          dispatch_async(watcher->queue_, ^{ | 
 |                            watcher->UpdateEventStream(root_change_at); | 
 |                          }); | 
 |                        }, | 
 |                        watcher->weak_factory_.GetWeakPtr(), root_change_at)); | 
 |   } | 
 |  | 
 |   watcher->OnFilePathsChanged(paths); | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::OnFilePathsChanged( | 
 |     const std::vector<FilePath>& paths) { | 
 |   DCHECK(!resolved_target_.empty()); | 
 |   task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       Bind(&FilePathWatcherFSEvents::DispatchEvents, weak_factory_.GetWeakPtr(), | 
 |            paths, target_, resolved_target_)); | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths, | 
 |                                              const FilePath& target, | 
 |                                              const FilePath& resolved_target) { | 
 |   DCHECK(task_runner()->RunsTasksInCurrentSequence()); | 
 |  | 
 |   // Don't issue callbacks after Cancel() has been called. | 
 |   if (is_cancelled() || callback_.is_null()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   for (const FilePath& path : paths) { | 
 |     if (resolved_target.IsParent(path) || resolved_target == path) { | 
 |       callback_.Run(target, false); | 
 |       return; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::UpdateEventStream( | 
 |     FSEventStreamEventId start_event) { | 
 |   // It can happen that the watcher gets canceled while tasks that call this | 
 |   // function are still in flight, so abort if this situation is detected. | 
 |   if (resolved_target_.empty()) | 
 |     return; | 
 |  | 
 |   if (fsevent_stream_) | 
 |     DestroyEventStream(); | 
 |  | 
 |   ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( | 
 |       NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS)); | 
 |   ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString( | 
 |       NULL, resolved_target_.DirName().value().c_str(), | 
 |       kCFStringEncodingMacHFS)); | 
 |   CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() }; | 
 |   ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate( | 
 |       NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array), | 
 |       &kCFTypeArrayCallBacks)); | 
 |  | 
 |   FSEventStreamContext context; | 
 |   context.version = 0; | 
 |   context.info = this; | 
 |   context.retain = NULL; | 
 |   context.release = NULL; | 
 |   context.copyDescription = NULL; | 
 |  | 
 |   fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, | 
 |                                         watched_paths, | 
 |                                         start_event, | 
 |                                         kEventLatencySeconds, | 
 |                                         kFSEventStreamCreateFlagWatchRoot); | 
 |   FSEventStreamSetDispatchQueue(fsevent_stream_, queue_); | 
 |  | 
 |   if (!FSEventStreamStart(fsevent_stream_)) { | 
 |     task_runner()->PostTask(FROM_HERE, | 
 |                             Bind(&FilePathWatcherFSEvents::ReportError, | 
 |                                  weak_factory_.GetWeakPtr(), target_)); | 
 |   } | 
 | } | 
 |  | 
 | bool FilePathWatcherFSEvents::ResolveTargetPath() { | 
 |   FilePath resolved = ResolvePath(target_).StripTrailingSeparators(); | 
 |   bool changed = resolved != resolved_target_; | 
 |   resolved_target_ = resolved; | 
 |   if (resolved_target_.empty()) { | 
 |     task_runner()->PostTask(FROM_HERE, | 
 |                             Bind(&FilePathWatcherFSEvents::ReportError, | 
 |                                  weak_factory_.GetWeakPtr(), target_)); | 
 |   } | 
 |   return changed; | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::ReportError(const FilePath& target) { | 
 |   DCHECK(task_runner()->RunsTasksInCurrentSequence()); | 
 |   if (!callback_.is_null()) { | 
 |     callback_.Run(target, true); | 
 |   } | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::DestroyEventStream() { | 
 |   FSEventStreamStop(fsevent_stream_); | 
 |   FSEventStreamInvalidate(fsevent_stream_); | 
 |   FSEventStreamRelease(fsevent_stream_); | 
 |   fsevent_stream_ = NULL; | 
 | } | 
 |  | 
 | void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event, | 
 |                                                const FilePath& path) { | 
 |   DCHECK(resolved_target_.empty()); | 
 |  | 
 |   target_ = path; | 
 |   ResolveTargetPath(); | 
 |   UpdateEventStream(start_event); | 
 | } | 
 |  | 
 | }  // namespace base |