| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/browser/file_path_watcher/file_path_watcher.h" | 5 #include "content/common/file_path_watcher/file_path_watcher.h" |
| 6 | 6 |
| 7 #include <errno.h> | 7 #include <errno.h> |
| 8 #include <string.h> | 8 #include <string.h> |
| 9 #include <sys/inotify.h> | 9 #include <sys/inotify.h> |
| 10 #include <sys/ioctl.h> | 10 #include <sys/ioctl.h> |
| 11 #include <sys/select.h> | 11 #include <sys/select.h> |
| 12 #include <unistd.h> | 12 #include <unistd.h> |
| 13 | 13 |
| 14 #include <algorithm> | 14 #include <algorithm> |
| 15 #include <set> | 15 #include <set> |
| 16 #include <utility> | 16 #include <utility> |
| 17 #include <vector> | 17 #include <vector> |
| 18 | 18 |
| 19 #include "base/eintr_wrapper.h" | 19 #include "base/eintr_wrapper.h" |
| 20 #include "base/file_path.h" | 20 #include "base/file_path.h" |
| 21 #include "base/file_util.h" | 21 #include "base/file_util.h" |
| 22 #include "base/hash_tables.h" | 22 #include "base/hash_tables.h" |
| 23 #include "base/lazy_instance.h" | 23 #include "base/lazy_instance.h" |
| 24 #include "base/logging.h" | 24 #include "base/logging.h" |
| 25 #include "base/message_loop.h" | 25 #include "base/message_loop.h" |
| 26 #include "base/message_loop_proxy.h" |
| 26 #include "base/scoped_ptr.h" | 27 #include "base/scoped_ptr.h" |
| 27 #include "base/synchronization/lock.h" | 28 #include "base/synchronization/lock.h" |
| 28 #include "base/task.h" | 29 #include "base/task.h" |
| 29 #include "base/threading/thread.h" | 30 #include "base/threading/thread.h" |
| 30 | 31 |
| 31 namespace { | 32 namespace { |
| 32 | 33 |
| 33 class FilePathWatcherImpl; | 34 class FilePathWatcherImpl; |
| 34 | 35 |
| 35 // Singleton to manage all inotify watches. | 36 // Singleton to manage all inotify watches. |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 72 | 73 |
| 73 // Use self-pipe trick to unblock select during shutdown. | 74 // Use self-pipe trick to unblock select during shutdown. |
| 74 int shutdown_pipe_[2]; | 75 int shutdown_pipe_[2]; |
| 75 | 76 |
| 76 // Flag set to true when startup was successful. | 77 // Flag set to true when startup was successful. |
| 77 bool valid_; | 78 bool valid_; |
| 78 | 79 |
| 79 DISALLOW_COPY_AND_ASSIGN(InotifyReader); | 80 DISALLOW_COPY_AND_ASSIGN(InotifyReader); |
| 80 }; | 81 }; |
| 81 | 82 |
| 82 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { | 83 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, |
| 84 public MessageLoop::DestructionObserver { |
| 83 public: | 85 public: |
| 84 FilePathWatcherImpl(); | 86 FilePathWatcherImpl(); |
| 85 | 87 |
| 86 // Called for each event coming from the watch. |fired_watch| identifies the | 88 // Called for each event coming from the watch. |fired_watch| identifies the |
| 87 // watch that fired, |child| indicates what has changed, and is relative to | 89 // watch that fired, |child| indicates what has changed, and is relative to |
| 88 // the currently watched path for |fired_watch|. The flag |created| is true if | 90 // the currently watched path for |fired_watch|. The flag |created| is true if |
| 89 // the object appears, and |is_directory| is set when the event refers to a | 91 // the object appears, and |is_directory| is set when the event refers to a |
| 90 // directory. | 92 // directory. |
| 91 void OnFilePathChanged(InotifyReader::Watch fired_watch, | 93 void OnFilePathChanged(InotifyReader::Watch fired_watch, |
| 92 const FilePath::StringType& child, | 94 const FilePath::StringType& child, |
| 93 bool created, | 95 bool created, |
| 94 bool is_directory); | 96 bool is_directory); |
| 95 | 97 |
| 96 // Start watching |path| for changes and notify |delegate| on each change. | 98 // Start watching |path| for changes and notify |delegate| on each change. |
| 97 // Returns true if watch for |path| has been added successfully. | 99 // Returns true if watch for |path| has been added successfully. |
| 98 virtual bool Watch(const FilePath& path, FilePathWatcher::Delegate* delegate); | 100 virtual bool Watch(const FilePath& path, |
| 101 FilePathWatcher::Delegate* delegate) OVERRIDE; |
| 99 | 102 |
| 100 // Cancel the watch. This unregisters the instance with InotifyReader. | 103 // Cancel the watch. This unregisters the instance with InotifyReader. |
| 101 virtual void Cancel(); | 104 virtual void Cancel() OVERRIDE; |
| 105 |
| 106 // Deletion of the FilePathWatcher will call Cancel() to dispose of this |
| 107 // object in the right thread. This also observes destruction of the required |
| 108 // cleanup thread, in case it quits before Cancel() is called. |
| 109 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; |
| 102 | 110 |
| 103 private: | 111 private: |
| 104 virtual ~FilePathWatcherImpl() {} | 112 virtual ~FilePathWatcherImpl() {} |
| 105 | 113 |
| 114 // Cleans up and stops observing the |message_loop_| thread. |
| 115 void CancelOnMessageLoopThread() OVERRIDE; |
| 116 |
| 106 // Inotify watches are installed for all directory components of |target_|. A | 117 // Inotify watches are installed for all directory components of |target_|. A |
| 107 // WatchEntry instance holds the watch descriptor for a component and the | 118 // WatchEntry instance holds the watch descriptor for a component and the |
| 108 // subdirectory for that identifies the next component. | 119 // subdirectory for that identifies the next component. |
| 109 struct WatchEntry { | 120 struct WatchEntry { |
| 110 WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir) | 121 WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir) |
| 111 : watch_(watch), | 122 : watch_(watch), |
| 112 subdir_(subdir) {} | 123 subdir_(subdir) {} |
| 113 | 124 |
| 114 InotifyReader::Watch watch_; | 125 InotifyReader::Watch watch_; |
| 115 FilePath::StringType subdir_; | 126 FilePath::StringType subdir_; |
| (...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 273 void InotifyReader::OnInotifyEvent(const inotify_event* event) { | 284 void InotifyReader::OnInotifyEvent(const inotify_event* event) { |
| 274 if (event->mask & IN_IGNORED) | 285 if (event->mask & IN_IGNORED) |
| 275 return; | 286 return; |
| 276 | 287 |
| 277 FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); | 288 FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); |
| 278 base::AutoLock auto_lock(lock_); | 289 base::AutoLock auto_lock(lock_); |
| 279 | 290 |
| 280 for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); | 291 for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); |
| 281 watcher != watchers_[event->wd].end(); | 292 watcher != watchers_[event->wd].end(); |
| 282 ++watcher) { | 293 ++watcher) { |
| 283 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 294 (*watcher)->OnFilePathChanged(event->wd, |
| 284 NewRunnableMethod(*watcher, | 295 child, |
| 285 &FilePathWatcherImpl::OnFilePathChanged, | 296 event->mask & (IN_CREATE | IN_MOVED_TO), |
| 286 event->wd, | 297 event->mask & IN_ISDIR); |
| 287 child, | |
| 288 event->mask & (IN_CREATE | IN_MOVED_TO), | |
| 289 event->mask & IN_ISDIR)); | |
| 290 } | 298 } |
| 291 } | 299 } |
| 292 | 300 |
| 293 FilePathWatcherImpl::FilePathWatcherImpl() | 301 FilePathWatcherImpl::FilePathWatcherImpl() |
| 294 : delegate_(NULL) { | 302 : delegate_(NULL) { |
| 295 } | 303 } |
| 296 | 304 |
| 297 void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch, | 305 void FilePathWatcherImpl::OnFilePathChanged( |
| 298 const FilePath::StringType& child, | 306 InotifyReader::Watch fired_watch, |
| 299 bool created, | 307 const FilePath::StringType& child, |
| 300 bool is_directory) { | 308 bool created, |
| 301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 309 bool is_directory) { |
| 310 |
| 311 if (!message_loop()->BelongsToCurrentThread()) { |
| 312 // Switch to message_loop_ to access watches_ safely. |
| 313 message_loop()->PostTask(FROM_HERE, |
| 314 NewRunnableMethod(this, |
| 315 &FilePathWatcherImpl::OnFilePathChanged, |
| 316 fired_watch, |
| 317 child, |
| 318 created, |
| 319 is_directory)); |
| 320 return; |
| 321 } |
| 322 |
| 323 DCHECK(MessageLoopForIO::current()); |
| 302 | 324 |
| 303 // Find the entry in |watches_| that corresponds to |fired_watch|. | 325 // Find the entry in |watches_| that corresponds to |fired_watch|. |
| 304 WatchVector::const_iterator watch_entry(watches_.begin()); | 326 WatchVector::const_iterator watch_entry(watches_.begin()); |
| 305 for ( ; watch_entry != watches_.end(); ++watch_entry) { | 327 for ( ; watch_entry != watches_.end(); ++watch_entry) { |
| 306 if (fired_watch == watch_entry->watch_) | 328 if (fired_watch == watch_entry->watch_) |
| 307 break; | 329 break; |
| 308 } | 330 } |
| 309 | 331 |
| 310 // If this notification is from a previous generation of watches or the watch | 332 // If this notification is from a previous generation of watches or the watch |
| 311 // has been cancelled (|watches_| is empty then), bail out. | 333 // has been cancelled (|watches_| is empty then), bail out. |
| 312 if (watch_entry == watches_.end()) | 334 if (watch_entry == watches_.end()) |
| 313 return; | 335 return; |
| 314 | 336 |
| 315 // Check whether a path component of |target_| changed. | 337 // Check whether a path component of |target_| changed. |
| 316 bool change_on_target_path = child.empty() || child == watch_entry->subdir_; | 338 bool change_on_target_path = child.empty() || child == watch_entry->subdir_; |
| 317 | 339 |
| 318 // Check whether the change references |target_| or a direct child. | 340 // Check whether the change references |target_| or a direct child. |
| 319 DCHECK(watch_entry->subdir_.empty() || (watch_entry + 1) != watches_.end()); | 341 DCHECK(watch_entry->subdir_.empty() || (watch_entry + 1) != watches_.end()); |
| 320 bool target_changed = watch_entry->subdir_.empty() || | 342 bool target_changed = watch_entry->subdir_.empty() || |
| 321 (watch_entry->subdir_ == child && (++watch_entry)->subdir_.empty()); | 343 (watch_entry->subdir_ == child && (++watch_entry)->subdir_.empty()); |
| 322 | 344 |
| 323 // Update watches if a directory component of the |target_| path (dis)appears. | 345 // Update watches if a directory component of the |target_| path (dis)appears. |
| 324 if (is_directory && change_on_target_path && !UpdateWatches()) { | 346 if (is_directory && change_on_target_path && !UpdateWatches()) { |
| 325 delegate_->OnError(); | 347 delegate_->OnFilePathError(target_); |
| 326 return; | 348 return; |
| 327 } | 349 } |
| 328 | 350 |
| 329 // Report the following events: | 351 // Report the following events: |
| 330 // - The target or a direct child of the target got changed (in case the | 352 // - The target or a direct child of the target got changed (in case the |
| 331 // watched path refers to a directory). | 353 // watched path refers to a directory). |
| 332 // - One of the parent directories got moved or deleted, since the target | 354 // - One of the parent directories got moved or deleted, since the target |
| 333 // disappears in this case. | 355 // disappears in this case. |
| 334 // - One of the parent directories appears. The event corresponding to the | 356 // - One of the parent directories appears. The event corresponding to the |
| 335 // target appearing might have been missed in this case, so recheck. | 357 // target appearing might have been missed in this case, so recheck. |
| 336 if (target_changed || | 358 if (target_changed || |
| 337 (change_on_target_path && !created) || | 359 (change_on_target_path && !created) || |
| 338 (change_on_target_path && file_util::PathExists(target_))) { | 360 (change_on_target_path && file_util::PathExists(target_))) { |
| 339 delegate_->OnFilePathChanged(target_); | 361 delegate_->OnFilePathChanged(target_); |
| 340 } | 362 } |
| 341 } | 363 } |
| 342 | 364 |
| 343 bool FilePathWatcherImpl::Watch(const FilePath& path, | 365 bool FilePathWatcherImpl::Watch(const FilePath& path, |
| 344 FilePathWatcher::Delegate* delegate) { | 366 FilePathWatcher::Delegate* delegate) { |
| 345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 346 DCHECK(target_.empty()); | 367 DCHECK(target_.empty()); |
| 368 DCHECK(MessageLoopForIO::current()); |
| 347 | 369 |
| 370 set_message_loop(base::MessageLoopProxy::CreateForCurrentThread()); |
| 348 delegate_ = delegate; | 371 delegate_ = delegate; |
| 349 target_ = path; | 372 target_ = path; |
| 373 MessageLoop::current()->AddDestructionObserver(this); |
| 374 |
| 350 std::vector<FilePath::StringType> comps; | 375 std::vector<FilePath::StringType> comps; |
| 351 target_.GetComponents(&comps); | 376 target_.GetComponents(&comps); |
| 352 DCHECK(!comps.empty()); | 377 DCHECK(!comps.empty()); |
| 353 for (std::vector<FilePath::StringType>::const_iterator comp(++comps.begin()); | 378 for (std::vector<FilePath::StringType>::const_iterator comp(++comps.begin()); |
| 354 comp != comps.end(); ++comp) { | 379 comp != comps.end(); ++comp) { |
| 355 watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, *comp)); | 380 watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, *comp)); |
| 356 } | 381 } |
| 357 watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, | 382 watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, |
| 358 FilePath::StringType())); | 383 FilePath::StringType())); |
| 359 return UpdateWatches(); | 384 return UpdateWatches(); |
| 360 } | 385 } |
| 361 | 386 |
| 362 void FilePathWatcherImpl::Cancel() { | 387 void FilePathWatcherImpl::Cancel() { |
| 363 // Switch to the file thread if necessary so we can access |watches_|. | 388 if (!delegate_) { |
| 364 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { | 389 // Watch was never called, or the |message_loop_| thread is already gone. |
| 365 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 390 set_cancelled(); |
| 366 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); | |
| 367 return; | 391 return; |
| 368 } | 392 } |
| 369 | 393 |
| 370 for (WatchVector::iterator watch_entry(watches_.begin()); | 394 // Switch to the message_loop_ if necessary so we can access |watches_|. |
| 371 watch_entry != watches_.end(); ++watch_entry) { | 395 if (!message_loop()->BelongsToCurrentThread()) { |
| 372 if (watch_entry->watch_ != InotifyReader::kInvalidWatch) | 396 message_loop()->PostTask(FROM_HERE, |
| 373 g_inotify_reader.Get().RemoveWatch(watch_entry->watch_, this); | 397 new FilePathWatcher::CancelTask(this)); |
| 398 } else { |
| 399 CancelOnMessageLoopThread(); |
| 374 } | 400 } |
| 375 watches_.clear(); | 401 } |
| 376 delegate_ = NULL; | 402 |
| 377 target_.clear(); | 403 void FilePathWatcherImpl::CancelOnMessageLoopThread() { |
| 404 if (!is_cancelled()) { |
| 405 set_cancelled(); |
| 406 MessageLoop::current()->RemoveDestructionObserver(this); |
| 407 |
| 408 for (WatchVector::iterator watch_entry(watches_.begin()); |
| 409 watch_entry != watches_.end(); ++watch_entry) { |
| 410 if (watch_entry->watch_ != InotifyReader::kInvalidWatch) |
| 411 g_inotify_reader.Get().RemoveWatch(watch_entry->watch_, this); |
| 412 } |
| 413 watches_.clear(); |
| 414 delegate_ = NULL; |
| 415 target_.clear(); |
| 416 } |
| 417 } |
| 418 |
| 419 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { |
| 420 CancelOnMessageLoopThread(); |
| 378 } | 421 } |
| 379 | 422 |
| 380 bool FilePathWatcherImpl::UpdateWatches() { | 423 bool FilePathWatcherImpl::UpdateWatches() { |
| 381 // Ensure this runs on the file thread exclusively in order to avoid | 424 // Ensure this runs on the message_loop_ exclusively in order to avoid |
| 382 // concurrency issues. | 425 // concurrency issues. |
| 383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 426 DCHECK(message_loop()->BelongsToCurrentThread()); |
| 384 | 427 |
| 385 // Walk the list of watches and update them as we go. | 428 // Walk the list of watches and update them as we go. |
| 386 FilePath path(FILE_PATH_LITERAL("/")); | 429 FilePath path(FILE_PATH_LITERAL("/")); |
| 387 bool path_valid = true; | 430 bool path_valid = true; |
| 388 for (WatchVector::iterator watch_entry(watches_.begin()); | 431 for (WatchVector::iterator watch_entry(watches_.begin()); |
| 389 watch_entry != watches_.end(); ++watch_entry) { | 432 watch_entry != watches_.end(); ++watch_entry) { |
| 390 InotifyReader::Watch old_watch = watch_entry->watch_; | 433 InotifyReader::Watch old_watch = watch_entry->watch_; |
| 391 if (path_valid) { | 434 if (path_valid) { |
| 392 watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); | 435 watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); |
| 393 if (watch_entry->watch_ == InotifyReader::kInvalidWatch) { | 436 if (watch_entry->watch_ == InotifyReader::kInvalidWatch) { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 404 } | 447 } |
| 405 | 448 |
| 406 return true; | 449 return true; |
| 407 } | 450 } |
| 408 | 451 |
| 409 } // namespace | 452 } // namespace |
| 410 | 453 |
| 411 FilePathWatcher::FilePathWatcher() { | 454 FilePathWatcher::FilePathWatcher() { |
| 412 impl_ = new FilePathWatcherImpl(); | 455 impl_ = new FilePathWatcherImpl(); |
| 413 } | 456 } |
| OLD | NEW |