file_path_watcher_fsevents.cc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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. #include "base/files/file_path_watcher_fsevents.h"
  5. #include <dispatch/dispatch.h>
  6. #include <algorithm>
  7. #include <list>
  8. #include "base/bind.h"
  9. #include "base/check.h"
  10. #include "base/files/file_util.h"
  11. #include "base/lazy_instance.h"
  12. #include "base/mac/scoped_cftyperef.h"
  13. #include "base/strings/stringprintf.h"
  14. #include "base/threading/scoped_blocking_call.h"
  15. #include "base/threading/sequenced_task_runner_handle.h"
  16. namespace base {
  17. namespace {
  18. // The latency parameter passed to FSEventsStreamCreate().
  19. const CFAbsoluteTime kEventLatencySeconds = 0.3;
  20. // Resolve any symlinks in the path.
  21. FilePath ResolvePath(const FilePath& path) {
  22. const unsigned kMaxLinksToResolve = 255;
  23. std::vector<FilePath::StringType> component_vector = path.GetComponents();
  24. std::list<FilePath::StringType>
  25. components(component_vector.begin(), component_vector.end());
  26. FilePath result;
  27. unsigned resolve_count = 0;
  28. while (resolve_count < kMaxLinksToResolve && !components.empty()) {
  29. FilePath component(*components.begin());
  30. components.pop_front();
  31. FilePath current;
  32. if (component.IsAbsolute()) {
  33. current = component;
  34. } else {
  35. current = result.Append(component);
  36. }
  37. FilePath target;
  38. if (ReadSymbolicLink(current, &target)) {
  39. if (target.IsAbsolute())
  40. result.clear();
  41. std::vector<FilePath::StringType> target_components =
  42. target.GetComponents();
  43. components.insert(components.begin(), target_components.begin(),
  44. target_components.end());
  45. resolve_count++;
  46. } else {
  47. result = current;
  48. }
  49. }
  50. if (resolve_count >= kMaxLinksToResolve)
  51. result.clear();
  52. return result;
  53. }
  54. } // namespace
  55. FilePathWatcherFSEvents::FilePathWatcherFSEvents()
  56. : queue_(dispatch_queue_create(
  57. base::StringPrintf("org.chromium.base.FilePathWatcher.%p", this)
  58. .c_str(),
  59. DISPATCH_QUEUE_SERIAL)) {}
  60. FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
  61. DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
  62. DCHECK(callback_.is_null())
  63. << "Cancel() must be called before FilePathWatcher is destroyed.";
  64. }
  65. bool FilePathWatcherFSEvents::Watch(const FilePath& path,
  66. Type type,
  67. const FilePathWatcher::Callback& callback) {
  68. DCHECK(!callback.is_null());
  69. DCHECK(callback_.is_null());
  70. // This class could support non-recursive watches, but that is currently
  71. // left to FilePathWatcherKQueue.
  72. if (type != Type::kRecursive)
  73. return false;
  74. set_task_runner(SequencedTaskRunnerHandle::Get());
  75. callback_ = callback;
  76. FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
  77. // The block runtime would implicitly capture the reference, not the object
  78. // it's referencing. Copy the path into a local, so that the value is
  79. // captured by the block's scope.
  80. const FilePath path_copy(path);
  81. dispatch_async(queue_, ^{
  82. StartEventStream(start_event, path_copy);
  83. });
  84. return true;
  85. }
  86. void FilePathWatcherFSEvents::Cancel() {
  87. set_cancelled();
  88. callback_.Reset();
  89. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  90. // Switch to the dispatch queue to tear down the event stream. As the queue is
  91. // owned by |this|, and this method is called from the destructor, execute the
  92. // block synchronously.
  93. dispatch_sync(queue_, ^{
  94. if (fsevent_stream_) {
  95. DestroyEventStream();
  96. target_.clear();
  97. resolved_target_.clear();
  98. }
  99. });
  100. }
  101. // static
  102. void FilePathWatcherFSEvents::FSEventsCallback(
  103. ConstFSEventStreamRef stream,
  104. void* event_watcher,
  105. size_t num_events,
  106. void* event_paths,
  107. const FSEventStreamEventFlags flags[],
  108. const FSEventStreamEventId event_ids[]) {
  109. FilePathWatcherFSEvents* watcher =
  110. reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
  111. bool root_changed = watcher->ResolveTargetPath();
  112. std::vector<FilePath> paths;
  113. FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
  114. for (size_t i = 0; i < num_events; i++) {
  115. if (flags[i] & kFSEventStreamEventFlagRootChanged)
  116. root_changed = true;
  117. if (event_ids[i])
  118. root_change_at = std::min(root_change_at, event_ids[i]);
  119. paths.push_back(FilePath(
  120. reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
  121. }
  122. // Reinitialize the event stream if we find changes to the root. This is
  123. // necessary since FSEvents doesn't report any events for the subtree after
  124. // the directory to be watched gets created.
  125. if (root_changed) {
  126. // Resetting the event stream from within the callback fails (FSEvents spews
  127. // bad file descriptor errors), so do the reset asynchronously.
  128. //
  129. // We can't dispatch_async a call to UpdateEventStream() directly because
  130. // there would be no guarantee that |watcher| still exists when it runs.
  131. //
  132. // Instead, bounce on task_runner() and use a WeakPtr to verify that
  133. // |watcher| still exists. If it does, dispatch_async a call to
  134. // UpdateEventStream(). Because the destructor of |watcher| runs on
  135. // task_runner() and calls dispatch_sync, it is guaranteed that |watcher|
  136. // still exists when UpdateEventStream() runs.
  137. watcher->task_runner()->PostTask(
  138. FROM_HERE, BindOnce(
  139. [](WeakPtr<FilePathWatcherFSEvents> weak_watcher,
  140. FSEventStreamEventId root_change_at) {
  141. if (!weak_watcher)
  142. return;
  143. FilePathWatcherFSEvents* watcher = weak_watcher.get();
  144. dispatch_async(watcher->queue_, ^{
  145. watcher->UpdateEventStream(root_change_at);
  146. });
  147. },
  148. watcher->weak_factory_.GetWeakPtr(), root_change_at));
  149. }
  150. watcher->OnFilePathsChanged(paths);
  151. }
  152. void FilePathWatcherFSEvents::OnFilePathsChanged(
  153. const std::vector<FilePath>& paths) {
  154. DCHECK(!resolved_target_.empty());
  155. task_runner()->PostTask(
  156. FROM_HERE,
  157. BindOnce(&FilePathWatcherFSEvents::DispatchEvents,
  158. weak_factory_.GetWeakPtr(), paths, target_, resolved_target_));
  159. }
  160. void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths,
  161. const FilePath& target,
  162. const FilePath& resolved_target) {
  163. DCHECK(task_runner()->RunsTasksInCurrentSequence());
  164. // Don't issue callbacks after Cancel() has been called.
  165. if (is_cancelled() || callback_.is_null()) {
  166. return;
  167. }
  168. for (const FilePath& path : paths) {
  169. if (resolved_target.IsParent(path) || resolved_target == path) {
  170. callback_.Run(target, false);
  171. return;
  172. }
  173. }
  174. }
  175. void FilePathWatcherFSEvents::UpdateEventStream(
  176. FSEventStreamEventId start_event) {
  177. // It can happen that the watcher gets canceled while tasks that call this
  178. // function are still in flight, so abort if this situation is detected.
  179. if (resolved_target_.empty())
  180. return;
  181. if (fsevent_stream_)
  182. DestroyEventStream();
  183. ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
  184. NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
  185. ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
  186. NULL, resolved_target_.DirName().value().c_str(),
  187. kCFStringEncodingMacHFS));
  188. CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
  189. ScopedCFTypeRef<CFArrayRef> watched_paths(
  190. CFArrayCreate(NULL, reinterpret_cast<const void**>(paths_array),
  191. std::size(paths_array), &kCFTypeArrayCallBacks));
  192. FSEventStreamContext context;
  193. context.version = 0;
  194. context.info = this;
  195. context.retain = NULL;
  196. context.release = NULL;
  197. context.copyDescription = NULL;
  198. fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
  199. watched_paths,
  200. start_event,
  201. kEventLatencySeconds,
  202. kFSEventStreamCreateFlagWatchRoot);
  203. FSEventStreamSetDispatchQueue(fsevent_stream_, queue_);
  204. if (!FSEventStreamStart(fsevent_stream_)) {
  205. task_runner()->PostTask(FROM_HERE,
  206. BindOnce(&FilePathWatcherFSEvents::ReportError,
  207. weak_factory_.GetWeakPtr(), target_));
  208. }
  209. }
  210. bool FilePathWatcherFSEvents::ResolveTargetPath() {
  211. FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
  212. bool changed = resolved != resolved_target_;
  213. resolved_target_ = resolved;
  214. if (resolved_target_.empty()) {
  215. task_runner()->PostTask(FROM_HERE,
  216. BindOnce(&FilePathWatcherFSEvents::ReportError,
  217. weak_factory_.GetWeakPtr(), target_));
  218. }
  219. return changed;
  220. }
  221. void FilePathWatcherFSEvents::ReportError(const FilePath& target) {
  222. DCHECK(task_runner()->RunsTasksInCurrentSequence());
  223. if (!callback_.is_null()) {
  224. callback_.Run(target, true);
  225. }
  226. }
  227. void FilePathWatcherFSEvents::DestroyEventStream() {
  228. FSEventStreamStop(fsevent_stream_);
  229. FSEventStreamInvalidate(fsevent_stream_);
  230. FSEventStreamRelease(fsevent_stream_);
  231. fsevent_stream_ = NULL;
  232. }
  233. void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event,
  234. const FilePath& path) {
  235. DCHECK(resolved_target_.empty());
  236. target_ = path;
  237. ResolveTargetPath();
  238. UpdateEventStream(start_event);
  239. }
  240. } // namespace base