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