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