| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/files/file_path_watcher.h" | 5 #include "base/files/file_path_watcher.h" |
| 6 | 6 |
| 7 #include <errno.h> | 7 #include <errno.h> |
| 8 #include <stddef.h> | 8 #include <stddef.h> |
| 9 #include <string.h> | 9 #include <string.h> |
| 10 #include <sys/inotify.h> | 10 #include <sys/inotify.h> |
| 11 #include <sys/ioctl.h> | 11 #include <sys/ioctl.h> |
| 12 #include <sys/select.h> | 12 #include <sys/select.h> |
| 13 #include <unistd.h> | 13 #include <unistd.h> |
| 14 | 14 |
| 15 #include <algorithm> | 15 #include <algorithm> |
| 16 #include <map> | 16 #include <map> |
| 17 #include <memory> | 17 #include <memory> |
| 18 #include <set> | 18 #include <set> |
| 19 #include <utility> | 19 #include <utility> |
| 20 #include <vector> | 20 #include <vector> |
| 21 | 21 |
| 22 #include "base/bind.h" | 22 #include "base/bind.h" |
| 23 #include "base/containers/hash_tables.h" | 23 #include "base/containers/hash_tables.h" |
| 24 #include "base/files/file_enumerator.h" | 24 #include "base/files/file_enumerator.h" |
| 25 #include "base/files/file_path.h" | 25 #include "base/files/file_path.h" |
| 26 #include "base/files/file_util.h" | 26 #include "base/files/file_util.h" |
| 27 #include "base/lazy_instance.h" | |
| 28 #include "base/location.h" | 27 #include "base/location.h" |
| 29 #include "base/logging.h" | 28 #include "base/logging.h" |
| 30 #include "base/macros.h" | 29 #include "base/macros.h" |
| 31 #include "base/memory/ptr_util.h" | 30 #include "base/memory/ptr_util.h" |
| 32 #include "base/memory/weak_ptr.h" | 31 #include "base/memory/weak_ptr.h" |
| 33 #include "base/posix/eintr_wrapper.h" | 32 #include "base/posix/eintr_wrapper.h" |
| 34 #include "base/single_thread_task_runner.h" | 33 #include "base/single_thread_task_runner.h" |
| 35 #include "base/stl_util.h" | 34 #include "base/stl_util.h" |
| 36 #include "base/synchronization/lock.h" | 35 #include "base/synchronization/lock.h" |
| 37 #include "base/threading/sequenced_task_runner_handle.h" | 36 #include "base/threading/sequenced_task_runner_handle.h" |
| (...skipping 18 matching lines...) Expand all Loading... |
| 56 // change. Returns kInvalidWatch on failure. | 55 // change. Returns kInvalidWatch on failure. |
| 57 Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher); | 56 Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher); |
| 58 | 57 |
| 59 // Remove |watch| if it's valid. | 58 // Remove |watch| if it's valid. |
| 60 void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher); | 59 void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher); |
| 61 | 60 |
| 62 // Callback for InotifyReaderTask. | 61 // Callback for InotifyReaderTask. |
| 63 void OnInotifyEvent(const inotify_event* event); | 62 void OnInotifyEvent(const inotify_event* event); |
| 64 | 63 |
| 65 private: | 64 private: |
| 66 friend struct DefaultLazyInstanceTraits<InotifyReader>; | 65 friend InotifyReader* GetInotifyReader(); |
| 67 | |
| 68 typedef std::set<FilePathWatcherImpl*> WatcherSet; | 66 typedef std::set<FilePathWatcherImpl*> WatcherSet; |
| 69 | 67 |
| 70 InotifyReader(); | 68 InotifyReader(); |
| 71 // There is no destructor because |g_inotify_reader| is a | 69 // There is no destructor because GetInotifyReader() is leaky. Having a |
| 72 // base::LazyInstace::Leaky object. Having a destructor causes build | 70 // destructor causes build issues with GCC 6 (http://crbug.com/636346). |
| 73 // issues with GCC 6 (http://crbug.com/636346). | |
| 74 | 71 |
| 75 // We keep track of which delegates want to be notified on which watches. | 72 // We keep track of which delegates want to be notified on which watches. |
| 76 hash_map<Watch, WatcherSet> watchers_; | 73 hash_map<Watch, WatcherSet> watchers_; |
| 77 | 74 |
| 78 // Lock to protect watchers_. | 75 // Lock to protect watchers_. |
| 79 Lock lock_; | 76 Lock lock_; |
| 80 | 77 |
| 81 // Separate thread on which we run blocking read for inotify events. | 78 // Separate thread on which we run blocking read for inotify events. |
| 82 Thread thread_; | 79 Thread thread_; |
| 83 | 80 |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 236 while (i < bytes_read) { | 233 while (i < bytes_read) { |
| 237 inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]); | 234 inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]); |
| 238 size_t event_size = sizeof(inotify_event) + event->len; | 235 size_t event_size = sizeof(inotify_event) + event->len; |
| 239 DCHECK(i + event_size <= static_cast<size_t>(bytes_read)); | 236 DCHECK(i + event_size <= static_cast<size_t>(bytes_read)); |
| 240 reader->OnInotifyEvent(event); | 237 reader->OnInotifyEvent(event); |
| 241 i += event_size; | 238 i += event_size; |
| 242 } | 239 } |
| 243 } | 240 } |
| 244 } | 241 } |
| 245 | 242 |
| 246 static LazyInstance<InotifyReader>::Leaky g_inotify_reader = | 243 InotifyReader* GetInotifyReader() { |
| 247 LAZY_INSTANCE_INITIALIZER; | 244 static auto inotify_reader = new InotifyReader(); |
| 245 return inotify_reader; |
| 246 } |
| 248 | 247 |
| 249 InotifyReader::InotifyReader() | 248 InotifyReader::InotifyReader() |
| 250 : thread_("inotify_reader"), | 249 : thread_("inotify_reader"), |
| 251 inotify_fd_(inotify_init()), | 250 inotify_fd_(inotify_init()), |
| 252 valid_(false) { | 251 valid_(false) { |
| 253 if (inotify_fd_ < 0) | 252 if (inotify_fd_ < 0) |
| 254 PLOG(ERROR) << "inotify_init() failed"; | 253 PLOG(ERROR) << "inotify_init() failed"; |
| 255 | 254 |
| 256 if (inotify_fd_ >= 0 && thread_.Start()) { | 255 if (inotify_fd_ >= 0 && thread_.Start()) { |
| 257 thread_.task_runner()->PostTask( | 256 thread_.task_runner()->PostTask( |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 451 return; | 450 return; |
| 452 } | 451 } |
| 453 | 452 |
| 454 DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 453 DCHECK(task_runner()->RunsTasksOnCurrentThread()); |
| 455 DCHECK(!is_cancelled()); | 454 DCHECK(!is_cancelled()); |
| 456 | 455 |
| 457 set_cancelled(); | 456 set_cancelled(); |
| 458 callback_.Reset(); | 457 callback_.Reset(); |
| 459 | 458 |
| 460 for (size_t i = 0; i < watches_.size(); ++i) | 459 for (size_t i = 0; i < watches_.size(); ++i) |
| 461 g_inotify_reader.Get().RemoveWatch(watches_[i].watch, this); | 460 GetInotifyReader()->RemoveWatch(watches_[i].watch, this); |
| 462 watches_.clear(); | 461 watches_.clear(); |
| 463 target_.clear(); | 462 target_.clear(); |
| 464 RemoveRecursiveWatches(); | 463 RemoveRecursiveWatches(); |
| 465 } | 464 } |
| 466 | 465 |
| 467 void FilePathWatcherImpl::UpdateWatches() { | 466 void FilePathWatcherImpl::UpdateWatches() { |
| 468 // Ensure this runs on the task_runner() exclusively in order to avoid | 467 // Ensure this runs on the task_runner() exclusively in order to avoid |
| 469 // concurrency issues. | 468 // concurrency issues. |
| 470 DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 469 DCHECK(task_runner()->RunsTasksOnCurrentThread()); |
| 471 DCHECK(HasValidWatchVector()); | 470 DCHECK(HasValidWatchVector()); |
| 472 | 471 |
| 473 // Walk the list of watches and update them as we go. | 472 // Walk the list of watches and update them as we go. |
| 474 FilePath path(FILE_PATH_LITERAL("/")); | 473 FilePath path(FILE_PATH_LITERAL("/")); |
| 475 for (size_t i = 0; i < watches_.size(); ++i) { | 474 for (size_t i = 0; i < watches_.size(); ++i) { |
| 476 WatchEntry& watch_entry = watches_[i]; | 475 WatchEntry& watch_entry = watches_[i]; |
| 477 InotifyReader::Watch old_watch = watch_entry.watch; | 476 InotifyReader::Watch old_watch = watch_entry.watch; |
| 478 watch_entry.watch = InotifyReader::kInvalidWatch; | 477 watch_entry.watch = InotifyReader::kInvalidWatch; |
| 479 watch_entry.linkname.clear(); | 478 watch_entry.linkname.clear(); |
| 480 watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this); | 479 watch_entry.watch = GetInotifyReader()->AddWatch(path, this); |
| 481 if (watch_entry.watch == InotifyReader::kInvalidWatch) { | 480 if (watch_entry.watch == InotifyReader::kInvalidWatch) { |
| 482 // Ignore the error code (beyond symlink handling) to attempt to add | 481 // Ignore the error code (beyond symlink handling) to attempt to add |
| 483 // watches on accessible children of unreadable directories. Note that | 482 // watches on accessible children of unreadable directories. Note that |
| 484 // this is a best-effort attempt; we may not catch events in this | 483 // this is a best-effort attempt; we may not catch events in this |
| 485 // scenario. | 484 // scenario. |
| 486 if (IsLink(path)) | 485 if (IsLink(path)) |
| 487 AddWatchForBrokenSymlink(path, &watch_entry); | 486 AddWatchForBrokenSymlink(path, &watch_entry); |
| 488 } | 487 } |
| 489 if (old_watch != watch_entry.watch) | 488 if (old_watch != watch_entry.watch) |
| 490 g_inotify_reader.Get().RemoveWatch(old_watch, this); | 489 GetInotifyReader()->RemoveWatch(old_watch, this); |
| 491 path = path.Append(watch_entry.subdir); | 490 path = path.Append(watch_entry.subdir); |
| 492 } | 491 } |
| 493 | 492 |
| 494 UpdateRecursiveWatches(InotifyReader::kInvalidWatch, | 493 UpdateRecursiveWatches(InotifyReader::kInvalidWatch, |
| 495 false /* is directory? */); | 494 false /* is directory? */); |
| 496 } | 495 } |
| 497 | 496 |
| 498 void FilePathWatcherImpl::UpdateRecursiveWatches( | 497 void FilePathWatcherImpl::UpdateRecursiveWatches( |
| 499 InotifyReader::Watch fired_watch, | 498 InotifyReader::Watch fired_watch, |
| 500 bool is_dir) { | 499 bool is_dir) { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 526 target_; | 525 target_; |
| 527 | 526 |
| 528 std::map<FilePath, InotifyReader::Watch>::iterator start_it = | 527 std::map<FilePath, InotifyReader::Watch>::iterator start_it = |
| 529 recursive_watches_by_path_.lower_bound(changed_dir); | 528 recursive_watches_by_path_.lower_bound(changed_dir); |
| 530 std::map<FilePath, InotifyReader::Watch>::iterator end_it = start_it; | 529 std::map<FilePath, InotifyReader::Watch>::iterator end_it = start_it; |
| 531 for (; end_it != recursive_watches_by_path_.end(); ++end_it) { | 530 for (; end_it != recursive_watches_by_path_.end(); ++end_it) { |
| 532 const FilePath& cur_path = end_it->first; | 531 const FilePath& cur_path = end_it->first; |
| 533 if (!changed_dir.IsParent(cur_path)) | 532 if (!changed_dir.IsParent(cur_path)) |
| 534 break; | 533 break; |
| 535 if (!DirectoryExists(cur_path)) | 534 if (!DirectoryExists(cur_path)) |
| 536 g_inotify_reader.Get().RemoveWatch(end_it->second, this); | 535 GetInotifyReader()->RemoveWatch(end_it->second, this); |
| 537 } | 536 } |
| 538 recursive_watches_by_path_.erase(start_it, end_it); | 537 recursive_watches_by_path_.erase(start_it, end_it); |
| 539 UpdateRecursiveWatchesForPath(changed_dir); | 538 UpdateRecursiveWatchesForPath(changed_dir); |
| 540 } | 539 } |
| 541 | 540 |
| 542 void FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) { | 541 void FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) { |
| 543 DCHECK(recursive_); | 542 DCHECK(recursive_); |
| 544 DCHECK(!path.empty()); | 543 DCHECK(!path.empty()); |
| 545 DCHECK(DirectoryExists(path)); | 544 DCHECK(DirectoryExists(path)); |
| 546 | 545 |
| 547 // Note: SHOW_SYM_LINKS exposes symlinks as symlinks, so they are ignored | 546 // Note: SHOW_SYM_LINKS exposes symlinks as symlinks, so they are ignored |
| 548 // rather than followed. Following symlinks can easily lead to the undesirable | 547 // rather than followed. Following symlinks can easily lead to the undesirable |
| 549 // situation where the entire file system is being watched. | 548 // situation where the entire file system is being watched. |
| 550 FileEnumerator enumerator( | 549 FileEnumerator enumerator( |
| 551 path, | 550 path, |
| 552 true /* recursive enumeration */, | 551 true /* recursive enumeration */, |
| 553 FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS); | 552 FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS); |
| 554 for (FilePath current = enumerator.Next(); | 553 for (FilePath current = enumerator.Next(); |
| 555 !current.empty(); | 554 !current.empty(); |
| 556 current = enumerator.Next()) { | 555 current = enumerator.Next()) { |
| 557 DCHECK(enumerator.GetInfo().IsDirectory()); | 556 DCHECK(enumerator.GetInfo().IsDirectory()); |
| 558 | 557 |
| 559 if (!ContainsKey(recursive_watches_by_path_, current)) { | 558 if (!ContainsKey(recursive_watches_by_path_, current)) { |
| 560 // Add new watches. | 559 // Add new watches. |
| 561 InotifyReader::Watch watch = | 560 InotifyReader::Watch watch = GetInotifyReader()->AddWatch(current, this); |
| 562 g_inotify_reader.Get().AddWatch(current, this); | |
| 563 TrackWatchForRecursion(watch, current); | 561 TrackWatchForRecursion(watch, current); |
| 564 } else { | 562 } else { |
| 565 // Update existing watches. | 563 // Update existing watches. |
| 566 InotifyReader::Watch old_watch = recursive_watches_by_path_[current]; | 564 InotifyReader::Watch old_watch = recursive_watches_by_path_[current]; |
| 567 DCHECK_NE(InotifyReader::kInvalidWatch, old_watch); | 565 DCHECK_NE(InotifyReader::kInvalidWatch, old_watch); |
| 568 InotifyReader::Watch watch = | 566 InotifyReader::Watch watch = GetInotifyReader()->AddWatch(current, this); |
| 569 g_inotify_reader.Get().AddWatch(current, this); | |
| 570 if (watch != old_watch) { | 567 if (watch != old_watch) { |
| 571 g_inotify_reader.Get().RemoveWatch(old_watch, this); | 568 GetInotifyReader()->RemoveWatch(old_watch, this); |
| 572 recursive_paths_by_watch_.erase(old_watch); | 569 recursive_paths_by_watch_.erase(old_watch); |
| 573 recursive_watches_by_path_.erase(current); | 570 recursive_watches_by_path_.erase(current); |
| 574 TrackWatchForRecursion(watch, current); | 571 TrackWatchForRecursion(watch, current); |
| 575 } | 572 } |
| 576 } | 573 } |
| 577 } | 574 } |
| 578 } | 575 } |
| 579 | 576 |
| 580 void FilePathWatcherImpl::TrackWatchForRecursion(InotifyReader::Watch watch, | 577 void FilePathWatcherImpl::TrackWatchForRecursion(InotifyReader::Watch watch, |
| 581 const FilePath& path) { | 578 const FilePath& path) { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 593 } | 590 } |
| 594 | 591 |
| 595 void FilePathWatcherImpl::RemoveRecursiveWatches() { | 592 void FilePathWatcherImpl::RemoveRecursiveWatches() { |
| 596 if (!recursive_) | 593 if (!recursive_) |
| 597 return; | 594 return; |
| 598 | 595 |
| 599 for (hash_map<InotifyReader::Watch, FilePath>::const_iterator it = | 596 for (hash_map<InotifyReader::Watch, FilePath>::const_iterator it = |
| 600 recursive_paths_by_watch_.begin(); | 597 recursive_paths_by_watch_.begin(); |
| 601 it != recursive_paths_by_watch_.end(); | 598 it != recursive_paths_by_watch_.end(); |
| 602 ++it) { | 599 ++it) { |
| 603 g_inotify_reader.Get().RemoveWatch(it->first, this); | 600 GetInotifyReader()->RemoveWatch(it->first, this); |
| 604 } | 601 } |
| 605 recursive_paths_by_watch_.clear(); | 602 recursive_paths_by_watch_.clear(); |
| 606 recursive_watches_by_path_.clear(); | 603 recursive_watches_by_path_.clear(); |
| 607 } | 604 } |
| 608 | 605 |
| 609 void FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path, | 606 void FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path, |
| 610 WatchEntry* watch_entry) { | 607 WatchEntry* watch_entry) { |
| 611 DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch); | 608 DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch); |
| 612 FilePath link; | 609 FilePath link; |
| 613 if (!ReadSymbolicLink(path, &link)) | 610 if (!ReadSymbolicLink(path, &link)) |
| 614 return; | 611 return; |
| 615 | 612 |
| 616 if (!link.IsAbsolute()) | 613 if (!link.IsAbsolute()) |
| 617 link = path.DirName().Append(link); | 614 link = path.DirName().Append(link); |
| 618 | 615 |
| 619 // Try watching symlink target directory. If the link target is "/", then we | 616 // 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 | 617 // 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 | 618 // changes to a component "/" which is harmless so no special treatment of |
| 622 // this case is required. | 619 // this case is required. |
| 623 InotifyReader::Watch watch = | 620 InotifyReader::Watch watch = |
| 624 g_inotify_reader.Get().AddWatch(link.DirName(), this); | 621 GetInotifyReader()->AddWatch(link.DirName(), this); |
| 625 if (watch == InotifyReader::kInvalidWatch) { | 622 if (watch == InotifyReader::kInvalidWatch) { |
| 626 // TODO(craig) Symlinks only work if the parent directory for the target | 623 // 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 | 624 // 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. | 625 // the symlink path for changes. See crbug.com/91561 for details. |
| 629 DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); | 626 DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); |
| 630 return; | 627 return; |
| 631 } | 628 } |
| 632 watch_entry->watch = watch; | 629 watch_entry->watch = watch; |
| 633 watch_entry->linkname = link.BaseName().value(); | 630 watch_entry->linkname = link.BaseName().value(); |
| 634 } | 631 } |
| 635 | 632 |
| 636 bool FilePathWatcherImpl::HasValidWatchVector() const { | 633 bool FilePathWatcherImpl::HasValidWatchVector() const { |
| 637 if (watches_.empty()) | 634 if (watches_.empty()) |
| 638 return false; | 635 return false; |
| 639 for (size_t i = 0; i < watches_.size() - 1; ++i) { | 636 for (size_t i = 0; i < watches_.size() - 1; ++i) { |
| 640 if (watches_[i].subdir.empty()) | 637 if (watches_[i].subdir.empty()) |
| 641 return false; | 638 return false; |
| 642 } | 639 } |
| 643 return watches_.back().subdir.empty(); | 640 return watches_.back().subdir.empty(); |
| 644 } | 641 } |
| 645 | 642 |
| 646 } // namespace | 643 } // namespace |
| 647 | 644 |
| 648 FilePathWatcher::FilePathWatcher() { | 645 FilePathWatcher::FilePathWatcher() { |
| 649 sequence_checker_.DetachFromSequence(); | 646 sequence_checker_.DetachFromSequence(); |
| 650 impl_ = MakeUnique<FilePathWatcherImpl>(); | 647 impl_ = MakeUnique<FilePathWatcherImpl>(); |
| 651 } | 648 } |
| 652 | 649 |
| 653 } // namespace base | 650 } // namespace base |
| OLD | NEW |