Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 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 "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 <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> |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 112 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; | 112 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; |
| 113 | 113 |
| 114 private: | 114 private: |
| 115 virtual ~FilePathWatcherImpl() {} | 115 virtual ~FilePathWatcherImpl() {} |
| 116 | 116 |
| 117 // Cleans up and stops observing the |message_loop_| thread. | 117 // Cleans up and stops observing the |message_loop_| thread. |
| 118 void CancelOnMessageLoopThread() OVERRIDE; | 118 void CancelOnMessageLoopThread() OVERRIDE; |
| 119 | 119 |
| 120 // Inotify watches are installed for all directory components of |target_|. A | 120 // Inotify watches are installed for all directory components of |target_|. A |
| 121 // WatchEntry instance holds the watch descriptor for a component and the | 121 // WatchEntry instance holds the watch descriptor for a component and the |
| 122 // subdirectory for that identifies the next component. | 122 // subdirectory for that identifies the next component. If a symbolic link |
| 123 // is being watched, the target of the link is also kept. | |
| 123 struct WatchEntry { | 124 struct WatchEntry { |
| 124 WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir) | 125 WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir) |
| 125 : watch_(watch), | 126 : watch_(watch), |
| 126 subdir_(subdir) {} | 127 subdir_(subdir) {} |
| 127 | 128 |
| 128 InotifyReader::Watch watch_; | 129 InotifyReader::Watch watch_; |
| 129 FilePath::StringType subdir_; | 130 FilePath::StringType subdir_; |
| 131 FilePath::StringType linkname_; | |
| 130 }; | 132 }; |
| 131 typedef std::vector<WatchEntry> WatchVector; | 133 typedef std::vector<WatchEntry> WatchVector; |
| 132 | 134 |
| 133 // Reconfigure to watch for the most specific parent directory of |target_| | 135 // Reconfigure to watch for the most specific parent directory of |target_| |
| 134 // that exists. Updates |watched_path_|. Returns true on success. | 136 // that exists. Updates |watched_path_|. Returns true on success. |
| 135 bool UpdateWatches() WARN_UNUSED_RESULT; | 137 bool UpdateWatches() WARN_UNUSED_RESULT; |
| 136 | 138 |
| 137 // Delegate to notify upon changes. | 139 // Delegate to notify upon changes. |
| 138 scoped_refptr<FilePathWatcher::Delegate> delegate_; | 140 scoped_refptr<FilePathWatcher::Delegate> delegate_; |
| 139 | 141 |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 321 created, | 323 created, |
| 322 is_directory)); | 324 is_directory)); |
| 323 return; | 325 return; |
| 324 } | 326 } |
| 325 | 327 |
| 326 DCHECK(MessageLoopForIO::current()); | 328 DCHECK(MessageLoopForIO::current()); |
| 327 | 329 |
| 328 // Find the entry in |watches_| that corresponds to |fired_watch|. | 330 // Find the entry in |watches_| that corresponds to |fired_watch|. |
| 329 WatchVector::const_iterator watch_entry(watches_.begin()); | 331 WatchVector::const_iterator watch_entry(watches_.begin()); |
| 330 for ( ; watch_entry != watches_.end(); ++watch_entry) { | 332 for ( ; watch_entry != watches_.end(); ++watch_entry) { |
| 331 if (fired_watch == watch_entry->watch_) | 333 if (fired_watch == watch_entry->watch_) { |
| 332 break; | 334 // Check whether a path component of |target_| changed. |
| 335 bool change_on_target_path = child.empty() || | |
| 336 ((child == watch_entry->subdir_) && watch_entry->linkname_.empty()) || | |
| 337 (child == watch_entry->linkname_); | |
| 338 | |
| 339 // Check whether the change references |target_| or a direct child. | |
| 340 DCHECK(watch_entry->subdir_.empty() || | |
| 341 (watch_entry + 1) != watches_.end()); | |
| 342 bool target_changed = | |
| 343 (watch_entry->subdir_.empty() && (child == watch_entry->linkname_)) || | |
| 344 (watch_entry->subdir_.empty() && watch_entry->linkname_.empty()) || | |
| 345 (watch_entry->subdir_ == child && (watch_entry + 1)->subdir_.empty()); | |
| 346 | |
| 347 // Update watches if a directory component of the |target_| path | |
| 348 // (dis)appears. Note that we don't add the additional restriction | |
| 349 // of checking is_directory here as changes to symlinks on the | |
| 350 // target path will not have is_directory set but as a result we | |
| 351 // may sometimes call UpdateWatches unnecessarily. | |
| 352 if (change_on_target_path && !UpdateWatches()) { | |
| 353 delegate_->OnFilePathError(target_); | |
| 354 return; | |
| 355 } | |
| 356 | |
| 357 // Report the following events: | |
| 358 // - The target or a direct child of the target got changed (in case the | |
| 359 // watched path refers to a directory). | |
| 360 // - One of the parent directories got moved or deleted, since the target | |
| 361 // disappears in this case. | |
| 362 // - One of the parent directories appears. The event corresponding to | |
| 363 // the target appearing might have been missed in this case, so | |
| 364 // recheck. | |
| 365 if (target_changed || | |
| 366 (change_on_target_path && !created) || | |
| 367 (change_on_target_path && file_util::PathExists(target_))) { | |
| 368 delegate_->OnFilePathChanged(target_); | |
| 369 return; | |
| 370 } | |
| 371 } | |
| 333 } | 372 } |
| 334 | 373 |
| 335 // If this notification is from a previous generation of watches or the watch | |
| 336 // has been cancelled (|watches_| is empty then), bail out. | |
| 337 if (watch_entry == watches_.end()) | |
| 338 return; | |
| 339 | |
| 340 // Check whether a path component of |target_| changed. | |
| 341 bool change_on_target_path = child.empty() || child == watch_entry->subdir_; | |
| 342 | |
| 343 // Check whether the change references |target_| or a direct child. | |
| 344 DCHECK(watch_entry->subdir_.empty() || (watch_entry + 1) != watches_.end()); | |
| 345 bool target_changed = watch_entry->subdir_.empty() || | |
| 346 (watch_entry->subdir_ == child && (++watch_entry)->subdir_.empty()); | |
| 347 | |
| 348 // Update watches if a directory component of the |target_| path (dis)appears. | |
| 349 if (is_directory && change_on_target_path && !UpdateWatches()) { | |
| 350 delegate_->OnFilePathError(target_); | |
| 351 return; | |
| 352 } | |
| 353 | |
| 354 // Report the following events: | |
| 355 // - The target or a direct child of the target got changed (in case the | |
| 356 // watched path refers to a directory). | |
| 357 // - One of the parent directories got moved or deleted, since the target | |
| 358 // disappears in this case. | |
| 359 // - One of the parent directories appears. The event corresponding to the | |
| 360 // target appearing might have been missed in this case, so recheck. | |
| 361 if (target_changed || | |
| 362 (change_on_target_path && !created) || | |
| 363 (change_on_target_path && file_util::PathExists(target_))) { | |
| 364 delegate_->OnFilePathChanged(target_); | |
| 365 } | |
| 366 } | 374 } |
| 367 | 375 |
| 368 bool FilePathWatcherImpl::Watch(const FilePath& path, | 376 bool FilePathWatcherImpl::Watch(const FilePath& path, |
| 369 FilePathWatcher::Delegate* delegate) { | 377 FilePathWatcher::Delegate* delegate) { |
| 370 DCHECK(target_.empty()); | 378 DCHECK(target_.empty()); |
| 371 DCHECK(MessageLoopForIO::current()); | 379 DCHECK(MessageLoopForIO::current()); |
| 372 | 380 |
| 373 set_message_loop(base::MessageLoopProxy::current()); | 381 set_message_loop(base::MessageLoopProxy::current()); |
| 374 delegate_ = delegate; | 382 delegate_ = delegate; |
| 375 target_ = path; | 383 target_ = path; |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 431 DCHECK(message_loop()->BelongsToCurrentThread()); | 439 DCHECK(message_loop()->BelongsToCurrentThread()); |
| 432 | 440 |
| 433 // Walk the list of watches and update them as we go. | 441 // Walk the list of watches and update them as we go. |
| 434 FilePath path(FILE_PATH_LITERAL("/")); | 442 FilePath path(FILE_PATH_LITERAL("/")); |
| 435 bool path_valid = true; | 443 bool path_valid = true; |
| 436 for (WatchVector::iterator watch_entry(watches_.begin()); | 444 for (WatchVector::iterator watch_entry(watches_.begin()); |
| 437 watch_entry != watches_.end(); ++watch_entry) { | 445 watch_entry != watches_.end(); ++watch_entry) { |
| 438 InotifyReader::Watch old_watch = watch_entry->watch_; | 446 InotifyReader::Watch old_watch = watch_entry->watch_; |
| 439 if (path_valid) { | 447 if (path_valid) { |
| 440 watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); | 448 watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); |
| 449 if ((watch_entry->watch_ == InotifyReader::kInvalidWatch) && | |
| 450 file_util::IsLink(path)) { | |
| 451 FilePath link; | |
| 452 file_util::ReadSymbolicLink(path, &link); | |
| 453 if (!link.IsAbsolute()) | |
| 454 link = path.DirName().Append(link); | |
| 455 // Try watching symlink target directory. | |
| 456 watch_entry->watch_ = | |
| 457 g_inotify_reader.Get().AddWatch(link.DirName(), this); | |
|
Mattias Nissler (ping if slow)
2011/09/02 11:34:32
What if link is "/", i.e. a symbolic link pointing
Craig
2011/09/03 07:46:05
It's unlikely we'd get here under normal condition
| |
| 458 if (watch_entry->watch_ != InotifyReader::kInvalidWatch) { | |
| 459 watch_entry->linkname_ = link.BaseName().value(); | |
| 460 } else { | |
| 461 DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); | |
| 462 // TODO(craig) Symlinks only work if the parent directory | |
| 463 // for the target exist. Ideally we should make sure we've | |
| 464 // watched all the components of the symlink path for | |
| 465 // changes. See crbug.com/91561 for details. | |
| 466 } | |
| 467 } | |
| 441 if (watch_entry->watch_ == InotifyReader::kInvalidWatch) { | 468 if (watch_entry->watch_ == InotifyReader::kInvalidWatch) { |
| 442 path_valid = false; | 469 path_valid = false; |
| 443 } | 470 } |
| 444 } else { | 471 } else { |
| 445 watch_entry->watch_ = InotifyReader::kInvalidWatch; | 472 watch_entry->watch_ = InotifyReader::kInvalidWatch; |
| 446 } | 473 } |
| 447 if (old_watch != InotifyReader::kInvalidWatch && | 474 if (old_watch != InotifyReader::kInvalidWatch && |
| 448 old_watch != watch_entry->watch_) { | 475 old_watch != watch_entry->watch_) { |
| 449 g_inotify_reader.Get().RemoveWatch(old_watch, this); | 476 g_inotify_reader.Get().RemoveWatch(old_watch, this); |
| 450 } | 477 } |
| 451 path = path.Append(watch_entry->subdir_); | 478 path = path.Append(watch_entry->subdir_); |
| 452 } | 479 } |
| 453 | 480 |
| 454 return true; | 481 return true; |
| 455 } | 482 } |
| 456 | 483 |
| 457 } // namespace | 484 } // namespace |
| 458 | 485 |
| 459 FilePathWatcher::FilePathWatcher() { | 486 FilePathWatcher::FilePathWatcher() { |
| 460 impl_ = new FilePathWatcherImpl(); | 487 impl_ = new FilePathWatcherImpl(); |
| 461 } | 488 } |
| 462 | 489 |
| 463 } // namespace files | 490 } // namespace files |
| 464 } // namespace base | 491 } // namespace base |
| OLD | NEW |