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.
5 #include "base/files/file_path_watcher_fsevents.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/message_loop/message_loop.h"
16 #include "base/thread_task_runner_handle.h"
22 // The latency parameter passed to FSEventsStreamCreate().
23 const CFAbsoluteTime kEventLatencySeconds
= 0.3;
25 class FSEventsTaskRunner
: public mac::LibDispatchTaskRunner
{
28 : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
32 ~FSEventsTaskRunner() override
{}
35 static LazyInstance
<FSEventsTaskRunner
>::Leaky g_task_runner
=
36 LAZY_INSTANCE_INITIALIZER
;
38 // Resolve any symlinks in the path.
39 FilePath
ResolvePath(const FilePath
& path
) {
40 const unsigned kMaxLinksToResolve
= 255;
42 std::vector
<FilePath::StringType
> component_vector
;
43 path
.GetComponents(&component_vector
);
44 std::list
<FilePath::StringType
>
45 components(component_vector
.begin(), component_vector
.end());
48 unsigned resolve_count
= 0;
49 while (resolve_count
< kMaxLinksToResolve
&& !components
.empty()) {
50 FilePath
component(*components
.begin());
51 components
.pop_front();
54 if (component
.IsAbsolute()) {
57 current
= result
.Append(component
);
61 if (ReadSymbolicLink(current
, &target
)) {
62 if (target
.IsAbsolute())
64 std::vector
<FilePath::StringType
> target_components
;
65 target
.GetComponents(&target_components
);
66 components
.insert(components
.begin(), target_components
.begin(),
67 target_components
.end());
74 if (resolve_count
>= kMaxLinksToResolve
)
81 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL
) {
84 bool FilePathWatcherFSEvents::Watch(const FilePath
& path
,
86 const FilePathWatcher::Callback
& callback
) {
87 DCHECK(MessageLoopForIO::current());
88 DCHECK(!callback
.is_null());
89 DCHECK(callback_
.is_null());
91 // This class could support non-recursive watches, but that is currently
92 // left to FilePathWatcherKQueue.
96 set_task_runner(ThreadTaskRunnerHandle::Get());
99 FSEventStreamEventId start_event
= FSEventsGetCurrentEventId();
100 g_task_runner
.Get().PostTask(
101 FROM_HERE
, Bind(&FilePathWatcherFSEvents::StartEventStream
, this,
106 void FilePathWatcherFSEvents::Cancel() {
110 // Switch to the dispatch queue thread to tear down the event stream.
111 g_task_runner
.Get().PostTask(
113 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread
, this));
117 void FilePathWatcherFSEvents::FSEventsCallback(
118 ConstFSEventStreamRef stream
,
122 const FSEventStreamEventFlags flags
[],
123 const FSEventStreamEventId event_ids
[]) {
124 FilePathWatcherFSEvents
* watcher
=
125 reinterpret_cast<FilePathWatcherFSEvents
*>(event_watcher
);
126 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
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
)
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());
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.
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 g_task_runner
.Get().PostTask(
148 Bind(&FilePathWatcherFSEvents::UpdateEventStream
, watcher
,
152 watcher
->OnFilePathsChanged(paths
);
155 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
156 // This method may be called on either the libdispatch or task_runner()
157 // thread. Checking callback_ on the libdispatch thread here is safe because
158 // it is executing in a task posted by Cancel() which first reset callback_.
159 // PostTask forms a sufficient memory barrier to ensure that the value is
160 // consistent on the target thread.
161 DCHECK(callback_
.is_null())
162 << "Cancel() must be called before FilePathWatcher is destroyed.";
165 void FilePathWatcherFSEvents::OnFilePathsChanged(
166 const std::vector
<FilePath
>& paths
) {
167 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
168 DCHECK(!resolved_target_
.empty());
169 task_runner()->PostTask(
170 FROM_HERE
, Bind(&FilePathWatcherFSEvents::DispatchEvents
, this, paths
,
171 target_
, resolved_target_
));
174 void FilePathWatcherFSEvents::DispatchEvents(const std::vector
<FilePath
>& paths
,
175 const FilePath
& target
,
176 const FilePath
& resolved_target
) {
177 DCHECK(task_runner()->RunsTasksOnCurrentThread());
179 // Don't issue callbacks after Cancel() has been called.
180 if (is_cancelled() || callback_
.is_null()) {
184 for (const FilePath
& path
: paths
) {
185 if (resolved_target
.IsParent(path
) || resolved_target
== path
) {
186 callback_
.Run(target
, false);
192 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
193 // For all other implementations, the "message loop thread" is the IO thread,
194 // as returned by task_runner(). This implementation, however, needs to
195 // cancel pending work on the Dispatch Queue thread.
196 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
198 if (fsevent_stream_
) {
199 DestroyEventStream();
201 resolved_target_
.clear();
205 void FilePathWatcherFSEvents::UpdateEventStream(
206 FSEventStreamEventId start_event
) {
207 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
209 // It can happen that the watcher gets canceled while tasks that call this
210 // function are still in flight, so abort if this situation is detected.
211 if (resolved_target_
.empty())
215 DestroyEventStream();
217 ScopedCFTypeRef
<CFStringRef
> cf_path(CFStringCreateWithCString(
218 NULL
, resolved_target_
.value().c_str(), kCFStringEncodingMacHFS
));
219 ScopedCFTypeRef
<CFStringRef
> cf_dir_path(CFStringCreateWithCString(
220 NULL
, resolved_target_
.DirName().value().c_str(),
221 kCFStringEncodingMacHFS
));
222 CFStringRef paths_array
[] = { cf_path
.get(), cf_dir_path
.get() };
223 ScopedCFTypeRef
<CFArrayRef
> watched_paths(CFArrayCreate(
224 NULL
, reinterpret_cast<const void**>(paths_array
), arraysize(paths_array
),
225 &kCFTypeArrayCallBacks
));
227 FSEventStreamContext context
;
230 context
.retain
= NULL
;
231 context
.release
= NULL
;
232 context
.copyDescription
= NULL
;
234 fsevent_stream_
= FSEventStreamCreate(NULL
, &FSEventsCallback
, &context
,
237 kEventLatencySeconds
,
238 kFSEventStreamCreateFlagWatchRoot
);
239 FSEventStreamSetDispatchQueue(fsevent_stream_
,
240 g_task_runner
.Get().GetDispatchQueue());
242 if (!FSEventStreamStart(fsevent_stream_
)) {
243 task_runner()->PostTask(
244 FROM_HERE
, Bind(&FilePathWatcherFSEvents::ReportError
, this, target_
));
248 bool FilePathWatcherFSEvents::ResolveTargetPath() {
249 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
250 FilePath resolved
= ResolvePath(target_
).StripTrailingSeparators();
251 bool changed
= resolved
!= resolved_target_
;
252 resolved_target_
= resolved
;
253 if (resolved_target_
.empty()) {
254 task_runner()->PostTask(
255 FROM_HERE
, Bind(&FilePathWatcherFSEvents::ReportError
, this, target_
));
260 void FilePathWatcherFSEvents::ReportError(const FilePath
& target
) {
261 DCHECK(task_runner()->RunsTasksOnCurrentThread());
262 if (!callback_
.is_null()) {
263 callback_
.Run(target
, true);
267 void FilePathWatcherFSEvents::DestroyEventStream() {
268 FSEventStreamStop(fsevent_stream_
);
269 FSEventStreamInvalidate(fsevent_stream_
);
270 FSEventStreamRelease(fsevent_stream_
);
271 fsevent_stream_
= NULL
;
274 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event
,
275 const FilePath
& path
) {
276 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
277 DCHECK(resolved_target_
.empty());
281 UpdateEventStream(start_event
);