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: 28 FSEventsTaskRunner() 29 : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") { 30 } 31 32 protected: 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. 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 82 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) { 83 } 84 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 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 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 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 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 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 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 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 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 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 268 void FilePathWatcherFSEvents::DestroyEventStream() { 269 FSEventStreamStop(fsevent_stream_); 270 FSEventStreamInvalidate(fsevent_stream_); 271 FSEventStreamRelease(fsevent_stream_); 272 fsevent_stream_ = NULL; 273 } 274 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