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
)
80 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL
) {
83 bool FilePathWatcherFSEvents::Watch(const FilePath
& path
,
85 const FilePathWatcher::Callback
& callback
) {
86 DCHECK(MessageLoopForIO::current());
87 DCHECK(!callback
.is_null());
88 DCHECK(callback_
.is_null());
90 // This class could support non-recursive watches, but that is currently
91 // left to FilePathWatcherKQueue.
95 set_message_loop(MessageLoopProxy::current());
98 FSEventStreamEventId start_event
= FSEventsGetCurrentEventId();
99 g_task_runner
.Get().PostTask(
100 FROM_HERE
, Bind(&FilePathWatcherFSEvents::StartEventStream
, this,
105 void FilePathWatcherFSEvents::Cancel() {
109 // Switch to the dispatch queue thread to tear down the event stream.
110 g_task_runner
.Get().PostTask(
112 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread
, this));
116 void FilePathWatcherFSEvents::FSEventsCallback(
117 ConstFSEventStreamRef stream
,
121 const FSEventStreamEventFlags flags
[],
122 const FSEventStreamEventId event_ids
[]) {
123 FilePathWatcherFSEvents
* watcher
=
124 reinterpret_cast<FilePathWatcherFSEvents
*>(event_watcher
);
125 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
127 bool root_changed
= watcher
->ResolveTargetPath();
128 std::vector
<FilePath
> paths
;
129 FSEventStreamEventId root_change_at
= FSEventStreamGetLatestEventId(stream
);
130 for (size_t i
= 0; i
< num_events
; i
++) {
131 if (flags
[i
] & kFSEventStreamEventFlagRootChanged
)
134 root_change_at
= std::min(root_change_at
, event_ids
[i
]);
135 paths
.push_back(FilePath(
136 reinterpret_cast<char**>(event_paths
)[i
]).StripTrailingSeparators());
139 // Reinitialize the event stream if we find changes to the root. This is
140 // necessary since FSEvents doesn't report any events for the subtree after
141 // the directory to be watched gets created.
143 // Resetting the event stream from within the callback fails (FSEvents spews
144 // bad file descriptor errors), so post a task to do the reset.
145 g_task_runner
.Get().PostTask(
147 Bind(&FilePathWatcherFSEvents::UpdateEventStream
, watcher
,
151 watcher
->OnFilePathsChanged(paths
);
154 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
155 // This method may be called on either the libdispatch or message_loop()
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.";
164 void FilePathWatcherFSEvents::OnFilePathsChanged(
165 const std::vector
<FilePath
>& paths
) {
166 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
167 DCHECK(!resolved_target_
.empty());
168 message_loop()->PostTask(
169 FROM_HERE
, Bind(&FilePathWatcherFSEvents::DispatchEvents
, this, paths
,
170 target_
, resolved_target_
));
173 void FilePathWatcherFSEvents::DispatchEvents(const std::vector
<FilePath
>& paths
,
174 const FilePath
& target
,
175 const FilePath
& resolved_target
) {
176 DCHECK(message_loop()->RunsTasksOnCurrentThread());
178 // Don't issue callbacks after Cancel() has been called.
179 if (is_cancelled() || callback_
.is_null()) {
183 for (const FilePath
& path
: paths
) {
184 if (resolved_target
.IsParent(path
) || resolved_target
== path
) {
185 callback_
.Run(target
, false);
191 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
192 // For all other implementations, the "message loop thread" is the IO thread,
193 // as returned by message_loop(). This implementation, however, needs to
194 // cancel pending work on the Dispatch Queue thread.
195 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
197 if (fsevent_stream_
) {
198 DestroyEventStream();
200 resolved_target_
.clear();
204 void FilePathWatcherFSEvents::UpdateEventStream(
205 FSEventStreamEventId start_event
) {
206 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
208 // It can happen that the watcher gets canceled while tasks that call this
209 // function are still in flight, so abort if this situation is detected.
210 if (resolved_target_
.empty())
214 DestroyEventStream();
216 ScopedCFTypeRef
<CFStringRef
> cf_path(CFStringCreateWithCString(
217 NULL
, resolved_target_
.value().c_str(), kCFStringEncodingMacHFS
));
218 ScopedCFTypeRef
<CFStringRef
> cf_dir_path(CFStringCreateWithCString(
219 NULL
, resolved_target_
.DirName().value().c_str(),
220 kCFStringEncodingMacHFS
));
221 CFStringRef paths_array
[] = { cf_path
.get(), cf_dir_path
.get() };
222 ScopedCFTypeRef
<CFArrayRef
> watched_paths(CFArrayCreate(
223 NULL
, reinterpret_cast<const void**>(paths_array
), arraysize(paths_array
),
224 &kCFTypeArrayCallBacks
));
226 FSEventStreamContext context
;
229 context
.retain
= NULL
;
230 context
.release
= NULL
;
231 context
.copyDescription
= NULL
;
233 fsevent_stream_
= FSEventStreamCreate(NULL
, &FSEventsCallback
, &context
,
236 kEventLatencySeconds
,
237 kFSEventStreamCreateFlagWatchRoot
);
238 FSEventStreamSetDispatchQueue(fsevent_stream_
,
239 g_task_runner
.Get().GetDispatchQueue());
241 if (!FSEventStreamStart(fsevent_stream_
)) {
242 message_loop()->PostTask(
243 FROM_HERE
, Bind(&FilePathWatcherFSEvents::ReportError
, this, target_
));
247 bool FilePathWatcherFSEvents::ResolveTargetPath() {
248 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
249 FilePath resolved
= ResolvePath(target_
).StripTrailingSeparators();
250 bool changed
= resolved
!= resolved_target_
;
251 resolved_target_
= resolved
;
252 if (resolved_target_
.empty()) {
253 message_loop()->PostTask(
254 FROM_HERE
, Bind(&FilePathWatcherFSEvents::ReportError
, this, target_
));
259 void FilePathWatcherFSEvents::ReportError(const FilePath
& target
) {
260 DCHECK(message_loop()->RunsTasksOnCurrentThread());
261 if (!callback_
.is_null()) {
262 callback_
.Run(target
, true);
266 void FilePathWatcherFSEvents::DestroyEventStream() {
267 FSEventStreamStop(fsevent_stream_
);
268 FSEventStreamInvalidate(fsevent_stream_
);
269 FSEventStreamRelease(fsevent_stream_
);
270 fsevent_stream_
= NULL
;
273 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event
,
274 const FilePath
& path
) {
275 DCHECK(g_task_runner
.Get().RunsTasksOnCurrentThread());
276 DCHECK(resolved_target_
.empty());
280 UpdateEventStream(start_event
);