1 // Copyright (c) 2012 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.h" 6 7 #include <errno.h> 8 #include <stddef.h> 9 #include <string.h> 10 #include <sys/inotify.h> 11 #include <sys/ioctl.h> 12 #include <sys/select.h> 13 #include <unistd.h> 14 15 #include <algorithm> 16 #include <map> 17 #include <memory> 18 #include <set> 19 #include <utility> 20 #include <vector> 21 22 #include "base/bind.h" 23 #include "base/containers/hash_tables.h" 24 #include "base/files/file_enumerator.h" 25 #include "base/files/file_path.h" 26 #include "base/files/file_util.h" 27 #include "base/lazy_instance.h" 28 #include "base/location.h" 29 #include "base/logging.h" 30 #include "base/macros.h" 31 #include "base/memory/ptr_util.h" 32 #include "base/memory/weak_ptr.h" 33 #include "base/posix/eintr_wrapper.h" 34 #include "base/single_thread_task_runner.h" 35 #include "base/stl_util.h" 36 #include "base/synchronization/lock.h" 37 #include "base/threading/sequenced_task_runner_handle.h" 38 #include "base/threading/thread.h" 39 #include "base/trace_event/trace_event.h" 40 41 namespace base { 42 43 namespace { 44 45 class FilePathWatcherImpl; 46 47 // Singleton to manage all inotify watches. 48 // TODO(tony): It would be nice if this wasn't a singleton. 49 // http://crbug.com/38174 50 class InotifyReader { 51 public: 52 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch. 53 static const Watch kInvalidWatch = -1; 54 55 // Watch directory |path| for changes. |watcher| will be notified on each 56 // change. Returns kInvalidWatch on failure. 57 Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher); 58 59 // Remove |watch| if it's valid. 60 void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher); 61 62 // Callback for InotifyReaderTask. 63 void OnInotifyEvent(const inotify_event* event); 64 65 private: 66 friend struct LazyInstanceTraitsBase<InotifyReader>; 67 68 typedef std::set<FilePathWatcherImpl*> WatcherSet; 69 70 InotifyReader(); 71 // There is no destructor because |g_inotify_reader| is a 72 // base::LazyInstace::Leaky object. Having a destructor causes build 73 // issues with GCC 6 (http://crbug.com/636346). 74 75 // We keep track of which delegates want to be notified on which watches. 76 hash_map<Watch, WatcherSet> watchers_; 77 78 // Lock to protect watchers_. 79 Lock lock_; 80 81 // Separate thread on which we run blocking read for inotify events. 82 Thread thread_; 83 84 // File descriptor returned by inotify_init. 85 const int inotify_fd_; 86 87 // Flag set to true when startup was successful. 88 bool valid_; 89 90 DISALLOW_COPY_AND_ASSIGN(InotifyReader); 91 }; 92 93 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { 94 public: 95 FilePathWatcherImpl(); 96 ~FilePathWatcherImpl() override; 97 98 // Called for each event coming from the watch. |fired_watch| identifies the 99 // watch that fired, |child| indicates what has changed, and is relative to 100 // the currently watched path for |fired_watch|. 101 // 102 // |created| is true if the object appears. 103 // |deleted| is true if the object disappears. 104 // |is_dir| is true if the object is a directory. 105 void OnFilePathChanged(InotifyReader::Watch fired_watch, 106 const FilePath::StringType& child, 107 bool created, 108 bool deleted, 109 bool is_dir); 110 111 private: 112 void OnFilePathChangedOnOriginSequence(InotifyReader::Watch fired_watch, 113 const FilePath::StringType& child, 114 bool created, 115 bool deleted, 116 bool is_dir); 117 118 // Start watching |path| for changes and notify |delegate| on each change. 119 // Returns true if watch for |path| has been added successfully. 120 bool Watch(const FilePath& path, 121 bool recursive, 122 const FilePathWatcher::Callback& callback) override; 123 124 // Cancel the watch. This unregisters the instance with InotifyReader. 125 void Cancel() override; 126 127 // Inotify watches are installed for all directory components of |target_|. 128 // A WatchEntry instance holds: 129 // - |watch|: the watch descriptor for a component. 130 // - |subdir|: the subdirectory that identifies the next component. 131 // - For the last component, there is no next component, so it is empty. 132 // - |linkname|: the target of the symlink. 133 // - Only if the target being watched is a symbolic link. 134 struct WatchEntry { 135 explicit WatchEntry(const FilePath::StringType& dirname) 136 : watch(InotifyReader::kInvalidWatch), 137 subdir(dirname) {} 138 139 InotifyReader::Watch watch; 140 FilePath::StringType subdir; 141 FilePath::StringType linkname; 142 }; 143 typedef std::vector<WatchEntry> WatchVector; 144 145 // Reconfigure to watch for the most specific parent directory of |target_| 146 // that exists. Also calls UpdateRecursiveWatches() below. 147 void UpdateWatches(); 148 149 // Reconfigure to recursively watch |target_| and all its sub-directories. 150 // - This is a no-op if the watch is not recursive. 151 // - If |target_| does not exist, then clear all the recursive watches. 152 // - Assuming |target_| exists, passing kInvalidWatch as |fired_watch| forces 153 // addition of recursive watches for |target_|. 154 // - Otherwise, only the directory associated with |fired_watch| and its 155 // sub-directories will be reconfigured. 156 void UpdateRecursiveWatches(InotifyReader::Watch fired_watch, bool is_dir); 157 158 // Enumerate recursively through |path| and add / update watches. 159 void UpdateRecursiveWatchesForPath(const FilePath& path); 160 161 // Do internal bookkeeping to update mappings between |watch| and its 162 // associated full path |path|. 163 void TrackWatchForRecursion(InotifyReader::Watch watch, const FilePath& path); 164 165 // Remove all the recursive watches. 166 void RemoveRecursiveWatches(); 167 168 // |path| is a symlink to a non-existent target. Attempt to add a watch to 169 // the link target's parent directory. Update |watch_entry| on success. 170 void AddWatchForBrokenSymlink(const FilePath& path, WatchEntry* watch_entry); 171 172 bool HasValidWatchVector() const; 173 174 // Callback to notify upon changes. 175 FilePathWatcher::Callback callback_; 176 177 // The file or directory we're supposed to watch. 178 FilePath target_; 179 180 bool recursive_; 181 182 // The vector of watches and next component names for all path components, 183 // starting at the root directory. The last entry corresponds to the watch for 184 // |target_| and always stores an empty next component name in |subdir|. 185 WatchVector watches_; 186 187 hash_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_; 188 std::map<FilePath, InotifyReader::Watch> recursive_watches_by_path_; 189 190 WeakPtrFactory<FilePathWatcherImpl> weak_factory_; 191 192 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); 193 }; 194 195 void InotifyReaderCallback(InotifyReader* reader, int inotify_fd) { 196 // Make sure the file descriptors are good for use with select(). 197 CHECK_LE(0, inotify_fd); 198 CHECK_GT(FD_SETSIZE, inotify_fd); 199 200 trace_event::TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop(); 201 202 while (true) { 203 fd_set rfds; 204 FD_ZERO(&rfds); 205 FD_SET(inotify_fd, &rfds); 206 207 // Wait until some inotify events are available. 208 int select_result = 209 HANDLE_EINTR(select(inotify_fd + 1, &rfds, NULL, NULL, NULL)); 210 if (select_result < 0) { 211 DPLOG(WARNING) << "select failed"; 212 return; 213 } 214 215 // Adjust buffer size to current event queue size. 216 int buffer_size; 217 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd, FIONREAD, 218 &buffer_size)); 219 220 if (ioctl_result != 0) { 221 DPLOG(WARNING) << "ioctl failed"; 222 return; 223 } 224 225 std::vector<char> buffer(buffer_size); 226 227 ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd, &buffer[0], 228 buffer_size)); 229 230 if (bytes_read < 0) { 231 DPLOG(WARNING) << "read from inotify fd failed"; 232 return; 233 } 234 235 ssize_t i = 0; 236 while (i < bytes_read) { 237 inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]); 238 size_t event_size = sizeof(inotify_event) + event->len; 239 DCHECK(i + event_size <= static_cast<size_t>(bytes_read)); 240 reader->OnInotifyEvent(event); 241 i += event_size; 242 } 243 } 244 } 245 246 static LazyInstance<InotifyReader>::Leaky g_inotify_reader = 247 LAZY_INSTANCE_INITIALIZER; 248 249 InotifyReader::InotifyReader() 250 : thread_("inotify_reader"), 251 inotify_fd_(inotify_init()), 252 valid_(false) { 253 if (inotify_fd_ < 0) 254 PLOG(ERROR) << "inotify_init() failed"; 255 256 if (inotify_fd_ >= 0 && thread_.Start()) { 257 thread_.task_runner()->PostTask( 258 FROM_HERE, 259 Bind(&InotifyReaderCallback, this, inotify_fd_)); 260 valid_ = true; 261 } 262 } 263 264 InotifyReader::Watch InotifyReader::AddWatch( 265 const FilePath& path, FilePathWatcherImpl* watcher) { 266 if (!valid_) 267 return kInvalidWatch; 268 269 AutoLock auto_lock(lock_); 270 271 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), 272 IN_ATTRIB | IN_CREATE | IN_DELETE | 273 IN_CLOSE_WRITE | IN_MOVE | 274 IN_ONLYDIR); 275 276 if (watch == kInvalidWatch) 277 return kInvalidWatch; 278 279 watchers_[watch].insert(watcher); 280 281 return watch; 282 } 283 284 void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) { 285 if (!valid_ || (watch == kInvalidWatch)) 286 return; 287 288 AutoLock auto_lock(lock_); 289 290 watchers_[watch].erase(watcher); 291 292 if (watchers_[watch].empty()) { 293 watchers_.erase(watch); 294 inotify_rm_watch(inotify_fd_, watch); 295 } 296 } 297 298 void InotifyReader::OnInotifyEvent(const inotify_event* event) { 299 if (event->mask & IN_IGNORED) 300 return; 301 302 FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); 303 AutoLock auto_lock(lock_); 304 305 for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); 306 watcher != watchers_[event->wd].end(); 307 ++watcher) { 308 (*watcher)->OnFilePathChanged(event->wd, 309 child, 310 event->mask & (IN_CREATE | IN_MOVED_TO), 311 event->mask & (IN_DELETE | IN_MOVED_FROM), 312 event->mask & IN_ISDIR); 313 } 314 } 315 316 FilePathWatcherImpl::FilePathWatcherImpl() 317 : recursive_(false), weak_factory_(this) {} 318 319 FilePathWatcherImpl::~FilePathWatcherImpl() { 320 DCHECK(!task_runner() || task_runner()->RunsTasksOnCurrentThread()); 321 } 322 323 void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch, 324 const FilePath::StringType& child, 325 bool created, 326 bool deleted, 327 bool is_dir) { 328 DCHECK(!task_runner()->RunsTasksOnCurrentThread()); 329 330 // This method is invoked on the Inotify thread. Switch to task_runner() to 331 // access |watches_| safely. Use a WeakPtr to prevent the callback from 332 // running after |this| is destroyed (i.e. after the watch is cancelled). 333 task_runner()->PostTask( 334 FROM_HERE, Bind(&FilePathWatcherImpl::OnFilePathChangedOnOriginSequence, 335 weak_factory_.GetWeakPtr(), fired_watch, child, created, 336 deleted, is_dir)); 337 } 338 339 void FilePathWatcherImpl::OnFilePathChangedOnOriginSequence( 340 InotifyReader::Watch fired_watch, 341 const FilePath::StringType& child, 342 bool created, 343 bool deleted, 344 bool is_dir) { 345 DCHECK(task_runner()->RunsTasksOnCurrentThread()); 346 DCHECK(!watches_.empty()); 347 DCHECK(HasValidWatchVector()); 348 349 // Used below to avoid multiple recursive updates. 350 bool did_update = false; 351 352 // Find the entry in |watches_| that corresponds to |fired_watch|. 353 for (size_t i = 0; i < watches_.size(); ++i) { 354 const WatchEntry& watch_entry = watches_[i]; 355 if (fired_watch != watch_entry.watch) 356 continue; 357 358 // Check whether a path component of |target_| changed. 359 bool change_on_target_path = 360 child.empty() || 361 (child == watch_entry.linkname) || 362 (child == watch_entry.subdir); 363 364 // Check if the change references |target_| or a direct child of |target_|. 365 bool target_changed; 366 if (watch_entry.subdir.empty()) { 367 // The fired watch is for a WatchEntry without a subdir. Thus for a given 368 // |target_| = "/path/to/foo", this is for "foo". Here, check either: 369 // - the target has no symlink: it is the target and it changed. 370 // - the target has a symlink, and it matches |child|. 371 target_changed = (watch_entry.linkname.empty() || 372 child == watch_entry.linkname); 373 } else { 374 // The fired watch is for a WatchEntry with a subdir. Thus for a given 375 // |target_| = "/path/to/foo", this is for {"/", "/path", "/path/to"}. 376 // So we can safely access the next WatchEntry since we have not reached 377 // the end yet. Check |watch_entry| is for "/path/to", i.e. the next 378 // element is "foo". 379 bool next_watch_may_be_for_target = watches_[i + 1].subdir.empty(); 380 if (next_watch_may_be_for_target) { 381 // The current |watch_entry| is for "/path/to", so check if the |child| 382 // that changed is "foo". 383 target_changed = watch_entry.subdir == child; 384 } else { 385 // The current |watch_entry| is not for "/path/to", so the next entry 386 // cannot be "foo". Thus |target_| has not changed. 387 target_changed = false; 388 } 389 } 390 391 // Update watches if a directory component of the |target_| path 392 // (dis)appears. Note that we don't add the additional restriction of 393 // checking the event mask to see if it is for a directory here as changes 394 // to symlinks on the target path will not have IN_ISDIR set in the event 395 // masks. As a result we may sometimes call UpdateWatches() unnecessarily. 396 if (change_on_target_path && (created || deleted) && !did_update) { 397 UpdateWatches(); 398 did_update = true; 399 } 400 401 // Report the following events: 402 // - The target or a direct child of the target got changed (in case the 403 // watched path refers to a directory). 404 // - One of the parent directories got moved or deleted, since the target 405 // disappears in this case. 406 // - One of the parent directories appears. The event corresponding to 407 // the target appearing might have been missed in this case, so recheck. 408 if (target_changed || 409 (change_on_target_path && deleted) || 410 (change_on_target_path && created && PathExists(target_))) { 411 if (!did_update) { 412 UpdateRecursiveWatches(fired_watch, is_dir); 413 did_update = true; 414 } 415 callback_.Run(target_, false /* error */); 416 return; 417 } 418 } 419 420 if (ContainsKey(recursive_paths_by_watch_, fired_watch)) { 421 if (!did_update) 422 UpdateRecursiveWatches(fired_watch, is_dir); 423 callback_.Run(target_, false /* error */); 424 } 425 } 426 427 bool FilePathWatcherImpl::Watch(const FilePath& path, 428 bool recursive, 429 const FilePathWatcher::Callback& callback) { 430 DCHECK(target_.empty()); 431 432 set_task_runner(SequencedTaskRunnerHandle::Get()); 433 callback_ = callback; 434 target_ = path; 435 recursive_ = recursive; 436 437 std::vector<FilePath::StringType> comps; 438 target_.GetComponents(&comps); 439 DCHECK(!comps.empty()); 440 for (size_t i = 1; i < comps.size(); ++i) 441 watches_.push_back(WatchEntry(comps[i])); 442 watches_.push_back(WatchEntry(FilePath::StringType())); 443 UpdateWatches(); 444 return true; 445 } 446 447 void FilePathWatcherImpl::Cancel() { 448 if (!callback_) { 449 // Watch() was never called. 450 set_cancelled(); 451 return; 452 } 453 454 DCHECK(task_runner()->RunsTasksOnCurrentThread()); 455 DCHECK(!is_cancelled()); 456 457 set_cancelled(); 458 callback_.Reset(); 459 460 for (size_t i = 0; i < watches_.size(); ++i) 461 g_inotify_reader.Get().RemoveWatch(watches_[i].watch, this); 462 watches_.clear(); 463 target_.clear(); 464 RemoveRecursiveWatches(); 465 } 466 467 void FilePathWatcherImpl::UpdateWatches() { 468 // Ensure this runs on the task_runner() exclusively in order to avoid 469 // concurrency issues. 470 DCHECK(task_runner()->RunsTasksOnCurrentThread()); 471 DCHECK(HasValidWatchVector()); 472 473 // Walk the list of watches and update them as we go. 474 FilePath path(FILE_PATH_LITERAL("/")); 475 for (size_t i = 0; i < watches_.size(); ++i) { 476 WatchEntry& watch_entry = watches_[i]; 477 InotifyReader::Watch old_watch = watch_entry.watch; 478 watch_entry.watch = InotifyReader::kInvalidWatch; 479 watch_entry.linkname.clear(); 480 watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this); 481 if (watch_entry.watch == InotifyReader::kInvalidWatch) { 482 // Ignore the error code (beyond symlink handling) to attempt to add 483 // watches on accessible children of unreadable directories. Note that 484 // this is a best-effort attempt; we may not catch events in this 485 // scenario. 486 if (IsLink(path)) 487 AddWatchForBrokenSymlink(path, &watch_entry); 488 } 489 if (old_watch != watch_entry.watch) 490 g_inotify_reader.Get().RemoveWatch(old_watch, this); 491 path = path.Append(watch_entry.subdir); 492 } 493 494 UpdateRecursiveWatches(InotifyReader::kInvalidWatch, 495 false /* is directory? */); 496 } 497 498 void FilePathWatcherImpl::UpdateRecursiveWatches( 499 InotifyReader::Watch fired_watch, 500 bool is_dir) { 501 DCHECK(HasValidWatchVector()); 502 503 if (!recursive_) 504 return; 505 506 if (!DirectoryExists(target_)) { 507 RemoveRecursiveWatches(); 508 return; 509 } 510 511 // Check to see if this is a forced update or if some component of |target_| 512 // has changed. For these cases, redo the watches for |target_| and below. 513 if (!ContainsKey(recursive_paths_by_watch_, fired_watch) && 514 fired_watch != watches_.back().watch) { 515 UpdateRecursiveWatchesForPath(target_); 516 return; 517 } 518 519 // Underneath |target_|, only directory changes trigger watch updates. 520 if (!is_dir) 521 return; 522 523 const FilePath& changed_dir = 524 ContainsKey(recursive_paths_by_watch_, fired_watch) ? 525 recursive_paths_by_watch_[fired_watch] : 526 target_; 527 528 std::map<FilePath, InotifyReader::Watch>::iterator start_it = 529 recursive_watches_by_path_.lower_bound(changed_dir); 530 std::map<FilePath, InotifyReader::Watch>::iterator end_it = start_it; 531 for (; end_it != recursive_watches_by_path_.end(); ++end_it) { 532 const FilePath& cur_path = end_it->first; 533 if (!changed_dir.IsParent(cur_path)) 534 break; 535 if (!DirectoryExists(cur_path)) 536 g_inotify_reader.Get().RemoveWatch(end_it->second, this); 537 } 538 recursive_watches_by_path_.erase(start_it, end_it); 539 UpdateRecursiveWatchesForPath(changed_dir); 540 } 541 542 void FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) { 543 DCHECK(recursive_); 544 DCHECK(!path.empty()); 545 DCHECK(DirectoryExists(path)); 546 547 // Note: SHOW_SYM_LINKS exposes symlinks as symlinks, so they are ignored 548 // rather than followed. Following symlinks can easily lead to the undesirable 549 // situation where the entire file system is being watched. 550 FileEnumerator enumerator( 551 path, 552 true /* recursive enumeration */, 553 FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS); 554 for (FilePath current = enumerator.Next(); 555 !current.empty(); 556 current = enumerator.Next()) { 557 DCHECK(enumerator.GetInfo().IsDirectory()); 558 559 if (!ContainsKey(recursive_watches_by_path_, current)) { 560 // Add new watches. 561 InotifyReader::Watch watch = 562 g_inotify_reader.Get().AddWatch(current, this); 563 TrackWatchForRecursion(watch, current); 564 } else { 565 // Update existing watches. 566 InotifyReader::Watch old_watch = recursive_watches_by_path_[current]; 567 DCHECK_NE(InotifyReader::kInvalidWatch, old_watch); 568 InotifyReader::Watch watch = 569 g_inotify_reader.Get().AddWatch(current, this); 570 if (watch != old_watch) { 571 g_inotify_reader.Get().RemoveWatch(old_watch, this); 572 recursive_paths_by_watch_.erase(old_watch); 573 recursive_watches_by_path_.erase(current); 574 TrackWatchForRecursion(watch, current); 575 } 576 } 577 } 578 } 579 580 void FilePathWatcherImpl::TrackWatchForRecursion(InotifyReader::Watch watch, 581 const FilePath& path) { 582 DCHECK(recursive_); 583 DCHECK(!path.empty()); 584 DCHECK(target_.IsParent(path)); 585 586 if (watch == InotifyReader::kInvalidWatch) 587 return; 588 589 DCHECK(!ContainsKey(recursive_paths_by_watch_, watch)); 590 DCHECK(!ContainsKey(recursive_watches_by_path_, path)); 591 recursive_paths_by_watch_[watch] = path; 592 recursive_watches_by_path_[path] = watch; 593 } 594 595 void FilePathWatcherImpl::RemoveRecursiveWatches() { 596 if (!recursive_) 597 return; 598 599 for (hash_map<InotifyReader::Watch, FilePath>::const_iterator it = 600 recursive_paths_by_watch_.begin(); 601 it != recursive_paths_by_watch_.end(); 602 ++it) { 603 g_inotify_reader.Get().RemoveWatch(it->first, this); 604 } 605 recursive_paths_by_watch_.clear(); 606 recursive_watches_by_path_.clear(); 607 } 608 609 void FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path, 610 WatchEntry* watch_entry) { 611 DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch); 612 FilePath link; 613 if (!ReadSymbolicLink(path, &link)) 614 return; 615 616 if (!link.IsAbsolute()) 617 link = path.DirName().Append(link); 618 619 // Try watching symlink target directory. If the link target is "/", then we 620 // shouldn't get here in normal situations and if we do, we'd watch "/" for 621 // changes to a component "/" which is harmless so no special treatment of 622 // this case is required. 623 InotifyReader::Watch watch = 624 g_inotify_reader.Get().AddWatch(link.DirName(), this); 625 if (watch == InotifyReader::kInvalidWatch) { 626 // TODO(craig) Symlinks only work if the parent directory for the target 627 // exist. Ideally we should make sure we've watched all the components of 628 // the symlink path for changes. See crbug.com/91561 for details. 629 DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); 630 return; 631 } 632 watch_entry->watch = watch; 633 watch_entry->linkname = link.BaseName().value(); 634 } 635 636 bool FilePathWatcherImpl::HasValidWatchVector() const { 637 if (watches_.empty()) 638 return false; 639 for (size_t i = 0; i < watches_.size() - 1; ++i) { 640 if (watches_[i].subdir.empty()) 641 return false; 642 } 643 return watches_.back().subdir.empty(); 644 } 645 646 } // namespace 647 648 FilePathWatcher::FilePathWatcher() { 649 sequence_checker_.DetachFromSequence(); 650 impl_ = MakeUnique<FilePathWatcherImpl>(); 651 } 652 653 } // namespace base 654