Home | History | Annotate | Download | only in files
      1 // Copyright 2014 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_fsevents.h"
      6 
      7 #include <list>
      8 
      9 #include "base/bind.h"
     10 #include "base/files/file_util.h"
     11 #include "base/lazy_instance.h"
     12 #include "base/logging.h"
     13 #include "base/mac/libdispatch_task_runner.h"
     14 #include "base/mac/scoped_cftyperef.h"
     15 #include "base/macros.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/thread_task_runner_handle.h"
     18 
     19 namespace base {
     20 
     21 namespace {
     22 
     23 // The latency parameter passed to FSEventsStreamCreate().
     24 const CFAbsoluteTime kEventLatencySeconds = 0.3;
     25 
     26 class FSEventsTaskRunner : public mac::LibDispatchTaskRunner {
     27  public:
     28    FSEventsTaskRunner()
     29        : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
     30    }
     31 
     32  protected:
     33   ~FSEventsTaskRunner() override {}
     34 };
     35 
     36 static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner =
     37     LAZY_INSTANCE_INITIALIZER;
     38 
     39 // Resolve any symlinks in the path.
     40 FilePath ResolvePath(const FilePath& path) {
     41   const unsigned kMaxLinksToResolve = 255;
     42 
     43   std::vector<FilePath::StringType> component_vector;
     44   path.GetComponents(&component_vector);
     45   std::list<FilePath::StringType>
     46       components(component_vector.begin(), component_vector.end());
     47 
     48   FilePath result;
     49   unsigned resolve_count = 0;
     50   while (resolve_count < kMaxLinksToResolve && !components.empty()) {
     51     FilePath component(*components.begin());
     52     components.pop_front();
     53 
     54     FilePath current;
     55     if (component.IsAbsolute()) {
     56       current = component;
     57     } else {
     58       current = result.Append(component);
     59     }
     60 
     61     FilePath target;
     62     if (ReadSymbolicLink(current, &target)) {
     63       if (target.IsAbsolute())
     64         result.clear();
     65       std::vector<FilePath::StringType> target_components;
     66       target.GetComponents(&target_components);
     67       components.insert(components.begin(), target_components.begin(),
     68                         target_components.end());
     69       resolve_count++;
     70     } else {
     71       result = current;
     72     }
     73   }
     74 
     75   if (resolve_count >= kMaxLinksToResolve)
     76     result.clear();
     77   return result;
     78 }
     79 
     80 }  // namespace
     81 
     82 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
     83 }
     84 
     85 bool FilePathWatcherFSEvents::Watch(const FilePath& path,
     86                                     bool recursive,
     87                                     const FilePathWatcher::Callback& callback) {
     88   DCHECK(MessageLoopForIO::current());
     89   DCHECK(!callback.is_null());
     90   DCHECK(callback_.is_null());
     91 
     92   // This class could support non-recursive watches, but that is currently
     93   // left to FilePathWatcherKQueue.
     94   if (!recursive)
     95     return false;
     96 
     97   set_task_runner(ThreadTaskRunnerHandle::Get());
     98   callback_ = callback;
     99 
    100   FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
    101   g_task_runner.Get().PostTask(
    102       FROM_HERE, Bind(&FilePathWatcherFSEvents::StartEventStream, this,
    103                       start_event, path));
    104   return true;
    105 }
    106 
    107 void FilePathWatcherFSEvents::Cancel() {
    108   set_cancelled();
    109   callback_.Reset();
    110 
    111   // Switch to the dispatch queue thread to tear down the event stream.
    112   g_task_runner.Get().PostTask(
    113       FROM_HERE,
    114       Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
    115 }
    116 
    117 // static
    118 void FilePathWatcherFSEvents::FSEventsCallback(
    119     ConstFSEventStreamRef stream,
    120     void* event_watcher,
    121     size_t num_events,
    122     void* event_paths,
    123     const FSEventStreamEventFlags flags[],
    124     const FSEventStreamEventId event_ids[]) {
    125   FilePathWatcherFSEvents* watcher =
    126       reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
    127   DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
    128 
    129   bool root_changed = watcher->ResolveTargetPath();
    130   std::vector<FilePath> paths;
    131   FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
    132   for (size_t i = 0; i < num_events; i++) {
    133     if (flags[i] & kFSEventStreamEventFlagRootChanged)
    134       root_changed = true;
    135     if (event_ids[i])
    136       root_change_at = std::min(root_change_at, event_ids[i]);
    137     paths.push_back(FilePath(
    138         reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
    139   }
    140 
    141   // Reinitialize the event stream if we find changes to the root. This is
    142   // necessary since FSEvents doesn't report any events for the subtree after
    143   // the directory to be watched gets created.
    144   if (root_changed) {
    145     // Resetting the event stream from within the callback fails (FSEvents spews
    146     // bad file descriptor errors), so post a task to do the reset.
    147     g_task_runner.Get().PostTask(
    148         FROM_HERE,
    149         Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher,
    150              root_change_at));
    151   }
    152 
    153   watcher->OnFilePathsChanged(paths);
    154 }
    155 
    156 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
    157   // This method may be called on either the libdispatch or task_runner()
    158   // thread. Checking callback_ on the libdispatch thread here is safe because
    159   // it is executing in a task posted by Cancel() which first reset callback_.
    160   // PostTask forms a sufficient memory barrier to ensure that the value is
    161   // consistent on the target thread.
    162   DCHECK(callback_.is_null())
    163       << "Cancel() must be called before FilePathWatcher is destroyed.";
    164 }
    165 
    166 void FilePathWatcherFSEvents::OnFilePathsChanged(
    167     const std::vector<FilePath>& paths) {
    168   DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
    169   DCHECK(!resolved_target_.empty());
    170   task_runner()->PostTask(
    171       FROM_HERE, Bind(&FilePathWatcherFSEvents::DispatchEvents, this, paths,
    172                       target_, resolved_target_));
    173 }
    174 
    175 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths,
    176                                              const FilePath& target,
    177                                              const FilePath& resolved_target) {
    178   DCHECK(task_runner()->RunsTasksOnCurrentThread());
    179 
    180   // Don't issue callbacks after Cancel() has been called.
    181   if (is_cancelled() || callback_.is_null()) {
    182     return;
    183   }
    184 
    185   for (const FilePath& path : paths) {
    186     if (resolved_target.IsParent(path) || resolved_target == path) {
    187       callback_.Run(target, false);
    188       return;
    189     }
    190   }
    191 }
    192 
    193 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
    194   // For all other implementations, the "message loop thread" is the IO thread,
    195   // as returned by task_runner(). This implementation, however, needs to
    196   // cancel pending work on the Dispatch Queue thread.
    197   DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
    198 
    199   if (fsevent_stream_) {
    200     DestroyEventStream();
    201     target_.clear();
    202     resolved_target_.clear();
    203   }
    204 }
    205 
    206 void FilePathWatcherFSEvents::UpdateEventStream(
    207     FSEventStreamEventId start_event) {
    208   DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
    209 
    210   // It can happen that the watcher gets canceled while tasks that call this
    211   // function are still in flight, so abort if this situation is detected.
    212   if (resolved_target_.empty())
    213     return;
    214 
    215   if (fsevent_stream_)
    216     DestroyEventStream();
    217 
    218   ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
    219       NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
    220   ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
    221       NULL, resolved_target_.DirName().value().c_str(),
    222       kCFStringEncodingMacHFS));
    223   CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
    224   ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
    225       NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
    226       &kCFTypeArrayCallBacks));
    227 
    228   FSEventStreamContext context;
    229   context.version = 0;
    230   context.info = this;
    231   context.retain = NULL;
    232   context.release = NULL;
    233   context.copyDescription = NULL;
    234 
    235   fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
    236                                         watched_paths,
    237                                         start_event,
    238                                         kEventLatencySeconds,
    239                                         kFSEventStreamCreateFlagWatchRoot);
    240   FSEventStreamSetDispatchQueue(fsevent_stream_,
    241                                 g_task_runner.Get().GetDispatchQueue());
    242 
    243   if (!FSEventStreamStart(fsevent_stream_)) {
    244     task_runner()->PostTask(
    245         FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_));
    246   }
    247 }
    248 
    249 bool FilePathWatcherFSEvents::ResolveTargetPath() {
    250   DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
    251   FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
    252   bool changed = resolved != resolved_target_;
    253   resolved_target_ = resolved;
    254   if (resolved_target_.empty()) {
    255     task_runner()->PostTask(
    256         FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_));
    257   }
    258   return changed;
    259 }
    260 
    261 void FilePathWatcherFSEvents::ReportError(const FilePath& target) {
    262   DCHECK(task_runner()->RunsTasksOnCurrentThread());
    263   if (!callback_.is_null()) {
    264     callback_.Run(target, true);
    265   }
    266 }
    267 
    268 void FilePathWatcherFSEvents::DestroyEventStream() {
    269   FSEventStreamStop(fsevent_stream_);
    270   FSEventStreamInvalidate(fsevent_stream_);
    271   FSEventStreamRelease(fsevent_stream_);
    272   fsevent_stream_ = NULL;
    273 }
    274 
    275 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event,
    276                                                const FilePath& path) {
    277   DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
    278   DCHECK(resolved_target_.empty());
    279 
    280   target_ = path;
    281   ResolveTargetPath();
    282   UpdateEventStream(start_event);
    283 }
    284 
    285 }  // namespace base
    286