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