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:
FSEventsTaskRunner()28    FSEventsTaskRunner()
29        : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
30    }
31 
32  protected:
~FSEventsTaskRunner()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.
ResolvePath(const FilePath & 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 
FilePathWatcherFSEvents()82 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
83 }
84 
Watch(const FilePath & path,bool recursive,const FilePathWatcher::Callback & callback)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 
Cancel()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
FSEventsCallback(ConstFSEventStreamRef stream,void * event_watcher,size_t num_events,void * event_paths,const FSEventStreamEventFlags flags[],const FSEventStreamEventId event_ids[])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 
~FilePathWatcherFSEvents()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 
OnFilePathsChanged(const std::vector<FilePath> & paths)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 
DispatchEvents(const std::vector<FilePath> & paths,const FilePath & target,const FilePath & resolved_target)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 
CancelOnMessageLoopThread()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 
UpdateEventStream(FSEventStreamEventId start_event)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 
ResolveTargetPath()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 
ReportError(const FilePath & target)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 
DestroyEventStream()268 void FilePathWatcherFSEvents::DestroyEventStream() {
269   FSEventStreamStop(fsevent_stream_);
270   FSEventStreamInvalidate(fsevent_stream_);
271   FSEventStreamRelease(fsevent_stream_);
272   fsevent_stream_ = NULL;
273 }
274 
StartEventStream(FSEventStreamEventId start_event,const FilePath & path)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