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"
21 // The latency parameter passed to FSEventsStreamCreate().
22 const CFAbsoluteTime kEventLatencySeconds
= 0.3;
24 class FSEventsTaskRunner
: public mac::LibDispatchTaskRunner
{
27 : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
31 ~FSEventsTaskRunner() override
{}
34 static LazyInstance
<FSEventsTaskRunner
>::Leaky g_task_runner
=
35 LAZY_INSTANCE_INITIALIZER
;
37 // Resolve any symlinks in the path.
38 FilePath
ResolvePath(const FilePath
& path
) {
39 const unsigned kMaxLinksToResolve
= 255;
41 std::vector
<FilePath::StringType
> component_vector
;
42 path
.GetComponents(&component_vector
);
43 std::list
<FilePath::StringType
>
44 components(component_vector
.begin(), component_vector
.end());
47 unsigned resolve_count
= 0;
48 while (resolve_count
< kMaxLinksToResolve
&& !components
.empty()) {
49 FilePath
component(*components
.begin());
50 components
.pop_front();
53 if (component
.IsAbsolute()) {
56 current
= result
.Append(component
);
60 if (ReadSymbolicLink(current
, &target
)) {
61 if (target
.IsAbsolute())
63 std::vector
<FilePath::StringType
> target_components
;
64 target
.GetComponents(&target_components
);
65 components
.insert(components
.begin(), target_components
.begin(),
66 target_components
.end());
73 if (resolve_count
>= kMaxLinksToResolve
)
78 // The callback passed to FSEventStreamCreate().
79 void FSEventsCallback(ConstFSEventStreamRef stream
,
80 void* event_watcher
, size_t num_events
,
81 void* event_paths
, const FSEventStreamEventFlags flags
[],
82 const FSEventStreamEventId event_ids
[]) {
83 FilePathWatcherFSEvents
* watcher
=
84 reinterpret_cast<FilePathWatcherFSEvents
*>(event_watcher
);
85 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
87 bool root_changed
= watcher
->ResolveTargetPath();
88 std::vector
<FilePath
> paths
;
89 FSEventStreamEventId root_change_at
= FSEventStreamGetLatestEventId(stream
);
90 for (size_t i
= 0; i
< num_events
; i
++) {
91 if (flags
[i
] & kFSEventStreamEventFlagRootChanged
)
94 root_change_at
= std::min(root_change_at
, event_ids
[i
]);
95 paths
.push_back(FilePath(
96 reinterpret_cast<char**>(event_paths
)[i
]).StripTrailingSeparators());
99 // Reinitialize the event stream if we find changes to the root. This is
100 // necessary since FSEvents doesn't report any events for the subtree after
101 // the directory to be watched gets created.
103 // Resetting the event stream from within the callback fails (FSEvents spews
104 // bad file descriptor errors), so post a task to do the reset.
105 g_task_runner
.Get().PostTask(
107 Bind(&FilePathWatcherFSEvents::UpdateEventStream
, watcher
,
111 watcher
->OnFilePathsChanged(paths
);
116 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL
) {
119 void FilePathWatcherFSEvents::OnFilePathsChanged(
120 const std::vector
<FilePath
>& paths
) {
121 if (!message_loop()->BelongsToCurrentThread()) {
122 message_loop()->PostTask(
124 Bind(&FilePathWatcherFSEvents::OnFilePathsChanged
, this, paths
));
128 DCHECK(message_loop()->BelongsToCurrentThread());
129 if (resolved_target_
.empty())
132 for (size_t i
= 0; i
< paths
.size(); i
++) {
133 if (resolved_target_
.IsParent(paths
[i
]) || resolved_target_
== paths
[i
]) {
134 callback_
.Run(target_
, false);
140 bool FilePathWatcherFSEvents::Watch(const FilePath
& path
,
142 const FilePathWatcher::Callback
& callback
) {
143 DCHECK(resolved_target_
.empty());
144 DCHECK(MessageLoopForIO::current());
145 DCHECK(!callback
.is_null());
147 // This class could support non-recursive watches, but that is currently
148 // left to FilePathWatcherKQueue.
152 set_message_loop(MessageLoopProxy::current());
153 callback_
= callback
;
156 FSEventStreamEventId start_event
= FSEventsGetCurrentEventId();
157 g_task_runner
.Get().PostTask(
159 Bind(&FilePathWatcherFSEvents::StartEventStream
, this, start_event
));
163 void FilePathWatcherFSEvents::Cancel() {
164 if (callback_
.is_null()) {
165 // Watch was never called, so exit.
170 // Switch to the dispatch queue thread if necessary, so we can tear down
172 if (!g_task_runner
.Get().RunsTasksOnCurrentThread()) {
173 g_task_runner
.Get().PostTask(
175 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread
, this));
177 CancelOnMessageLoopThread();
181 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
182 // For all other implementations, the "message loop thread" is the IO thread,
183 // as returned by message_loop(). This implementation, however, needs to
184 // cancel pending work on the Dipatch Queue thread.
185 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
188 if (fsevent_stream_
) {
189 DestroyEventStream();
192 resolved_target_
.clear();
196 void FilePathWatcherFSEvents::UpdateEventStream(
197 FSEventStreamEventId start_event
) {
198 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
200 // It can happen that the watcher gets canceled while tasks that call this
201 // function are still in flight, so abort if this situation is detected.
202 if (is_cancelled() || resolved_target_
.empty())
206 DestroyEventStream();
208 ScopedCFTypeRef
<CFStringRef
> cf_path(CFStringCreateWithCString(
209 NULL
, resolved_target_
.value().c_str(), kCFStringEncodingMacHFS
));
210 ScopedCFTypeRef
<CFStringRef
> cf_dir_path(CFStringCreateWithCString(
211 NULL
, resolved_target_
.DirName().value().c_str(),
212 kCFStringEncodingMacHFS
));
213 CFStringRef paths_array
[] = { cf_path
.get(), cf_dir_path
.get() };
214 ScopedCFTypeRef
<CFArrayRef
> watched_paths(CFArrayCreate(
215 NULL
, reinterpret_cast<const void**>(paths_array
), arraysize(paths_array
),
216 &kCFTypeArrayCallBacks
));
218 FSEventStreamContext context
;
221 context
.retain
= NULL
;
222 context
.release
= NULL
;
223 context
.copyDescription
= NULL
;
225 fsevent_stream_
= FSEventStreamCreate(NULL
, &FSEventsCallback
, &context
,
228 kEventLatencySeconds
,
229 kFSEventStreamCreateFlagWatchRoot
);
230 FSEventStreamSetDispatchQueue(fsevent_stream_
,
231 g_task_runner
.Get().GetDispatchQueue());
233 if (!FSEventStreamStart(fsevent_stream_
))
234 message_loop()->PostTask(FROM_HERE
, Bind(callback_
, target_
, true));
237 bool FilePathWatcherFSEvents::ResolveTargetPath() {
238 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
239 FilePath resolved
= ResolvePath(target_
).StripTrailingSeparators();
240 bool changed
= resolved
!= resolved_target_
;
241 resolved_target_
= resolved
;
242 if (resolved_target_
.empty())
243 message_loop()->PostTask(FROM_HERE
, Bind(callback_
, target_
, true));
247 void FilePathWatcherFSEvents::DestroyEventStream() {
248 FSEventStreamStop(fsevent_stream_
);
249 FSEventStreamInvalidate(fsevent_stream_
);
250 FSEventStreamRelease(fsevent_stream_
);
251 fsevent_stream_
= NULL
;
254 void FilePathWatcherFSEvents::StartEventStream(
255 FSEventStreamEventId start_event
) {
256 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
258 UpdateEventStream(start_event
);
261 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {}