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/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 17 namespace base { 18 19 namespace { 20 21 // The latency parameter passed to FSEventsStreamCreate(). 22 const CFAbsoluteTime kEventLatencySeconds = 0.3; 23 24 class FSEventsTaskRunner : public mac::LibDispatchTaskRunner { 25 public: 26 FSEventsTaskRunner() 27 : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") { 28 } 29 30 protected: 31 virtual ~FSEventsTaskRunner() {} 32 }; 33 34 static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner = 35 LAZY_INSTANCE_INITIALIZER; 36 37 // Resolve any symlinks in the path. 38 FilePath ResolvePath(const FilePath& path) { 39 const unsigned kMaxLinksToResolve = 255; 40 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()); 45 46 FilePath result; 47 unsigned resolve_count = 0; 48 while (resolve_count < kMaxLinksToResolve && !components.empty()) { 49 FilePath component(*components.begin()); 50 components.pop_front(); 51 52 FilePath current; 53 if (component.IsAbsolute()) { 54 current = component; 55 } else { 56 current = result.Append(component); 57 } 58 59 FilePath target; 60 if (ReadSymbolicLink(current, &target)) { 61 if (target.IsAbsolute()) 62 result.clear(); 63 std::vector<FilePath::StringType> target_components; 64 target.GetComponents(&target_components); 65 components.insert(components.begin(), target_components.begin(), 66 target_components.end()); 67 resolve_count++; 68 } else { 69 result = current; 70 } 71 } 72 73 if (resolve_count >= kMaxLinksToResolve) 74 result.clear(); 75 return result; 76 } 77 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()); 86 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) 92 root_changed = true; 93 if (event_ids[i]) 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()); 97 } 98 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. 102 if (root_changed) { 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( 106 FROM_HERE, 107 Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher, 108 root_change_at)); 109 } 110 111 watcher->OnFilePathsChanged(paths); 112 } 113 114 } // namespace 115 116 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) { 117 } 118 119 void FilePathWatcherFSEvents::OnFilePathsChanged( 120 const std::vector<FilePath>& paths) { 121 if (!message_loop()->BelongsToCurrentThread()) { 122 message_loop()->PostTask( 123 FROM_HERE, 124 Bind(&FilePathWatcherFSEvents::OnFilePathsChanged, this, paths)); 125 return; 126 } 127 128 DCHECK(message_loop()->BelongsToCurrentThread()); 129 if (resolved_target_.empty()) 130 return; 131 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); 135 return; 136 } 137 } 138 } 139 140 bool FilePathWatcherFSEvents::Watch(const FilePath& path, 141 bool recursive, 142 const FilePathWatcher::Callback& callback) { 143 DCHECK(resolved_target_.empty()); 144 DCHECK(MessageLoopForIO::current()); 145 DCHECK(!callback.is_null()); 146 147 // This class could support non-recursive watches, but that is currently 148 // left to FilePathWatcherKQueue. 149 if (!recursive) 150 return false; 151 152 set_message_loop(MessageLoopProxy::current()); 153 callback_ = callback; 154 target_ = path; 155 156 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); 157 g_task_runner.Get().PostTask( 158 FROM_HERE, 159 Bind(&FilePathWatcherFSEvents::StartEventStream, this, start_event)); 160 return true; 161 } 162 163 void FilePathWatcherFSEvents::Cancel() { 164 if (callback_.is_null()) { 165 // Watch was never called, so exit. 166 set_cancelled(); 167 return; 168 } 169 170 // Switch to the dispatch queue thread if necessary, so we can tear down 171 // the event stream. 172 if (!g_task_runner.Get().RunsTasksOnCurrentThread()) { 173 g_task_runner.Get().PostTask( 174 FROM_HERE, 175 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this)); 176 } else { 177 CancelOnMessageLoopThread(); 178 } 179 } 180 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()); 186 187 set_cancelled(); 188 if (fsevent_stream_) { 189 DestroyEventStream(); 190 callback_.Reset(); 191 target_.clear(); 192 resolved_target_.clear(); 193 } 194 } 195 196 void FilePathWatcherFSEvents::UpdateEventStream( 197 FSEventStreamEventId start_event) { 198 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); 199 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()) 203 return; 204 205 if (fsevent_stream_) 206 DestroyEventStream(); 207 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)); 217 218 FSEventStreamContext context; 219 context.version = 0; 220 context.info = this; 221 context.retain = NULL; 222 context.release = NULL; 223 context.copyDescription = NULL; 224 225 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, 226 watched_paths, 227 start_event, 228 kEventLatencySeconds, 229 kFSEventStreamCreateFlagWatchRoot); 230 FSEventStreamSetDispatchQueue(fsevent_stream_, 231 g_task_runner.Get().GetDispatchQueue()); 232 233 if (!FSEventStreamStart(fsevent_stream_)) 234 message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true)); 235 } 236 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)); 244 return changed; 245 } 246 247 void FilePathWatcherFSEvents::DestroyEventStream() { 248 FSEventStreamStop(fsevent_stream_); 249 FSEventStreamInvalidate(fsevent_stream_); 250 FSEventStreamRelease(fsevent_stream_); 251 fsevent_stream_ = NULL; 252 } 253 254 void FilePathWatcherFSEvents::StartEventStream( 255 FSEventStreamEventId start_event) { 256 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); 257 ResolveTargetPath(); 258 UpdateEventStream(start_event); 259 } 260 261 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {} 262 263 } // namespace base 264