file_path_watcher_linux.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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 <string.h> 9#include <sys/inotify.h> 10#include <sys/ioctl.h> 11#include <sys/select.h> 12#include <unistd.h> 13 14#include <algorithm> 15#include <set> 16#include <utility> 17#include <vector> 18 19#include "base/bind.h" 20#include "base/containers/hash_tables.h" 21#include "base/debug/trace_event.h" 22#include "base/file_util.h" 23#include "base/files/file_path.h" 24#include "base/lazy_instance.h" 25#include "base/location.h" 26#include "base/logging.h" 27#include "base/memory/scoped_ptr.h" 28#include "base/message_loop/message_loop.h" 29#include "base/message_loop/message_loop_proxy.h" 30#include "base/posix/eintr_wrapper.h" 31#include "base/synchronization/lock.h" 32#include "base/threading/thread.h" 33 34namespace base { 35 36namespace { 37 38class FilePathWatcherImpl; 39 40// Singleton to manage all inotify watches. 41// TODO(tony): It would be nice if this wasn't a singleton. 42// http://crbug.com/38174 43class InotifyReader { 44 public: 45 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch. 46 static const Watch kInvalidWatch = -1; 47 48 // Watch directory |path| for changes. |watcher| will be notified on each 49 // change. Returns kInvalidWatch on failure. 50 Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher); 51 52 // Remove |watch| if it's valid. 53 void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher); 54 55 // Callback for InotifyReaderTask. 56 void OnInotifyEvent(const inotify_event* event); 57 58 private: 59 friend struct DefaultLazyInstanceTraits<InotifyReader>; 60 61 typedef std::set<FilePathWatcherImpl*> WatcherSet; 62 63 InotifyReader(); 64 ~InotifyReader(); 65 66 // We keep track of which delegates want to be notified on which watches. 67 hash_map<Watch, WatcherSet> watchers_; 68 69 // Lock to protect watchers_. 70 Lock lock_; 71 72 // Separate thread on which we run blocking read for inotify events. 73 Thread thread_; 74 75 // File descriptor returned by inotify_init. 76 const int inotify_fd_; 77 78 // Use self-pipe trick to unblock select during shutdown. 79 int shutdown_pipe_[2]; 80 81 // Flag set to true when startup was successful. 82 bool valid_; 83 84 DISALLOW_COPY_AND_ASSIGN(InotifyReader); 85}; 86 87class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, 88 public MessageLoop::DestructionObserver { 89 public: 90 FilePathWatcherImpl(); 91 92 // Called for each event coming from the watch. |fired_watch| identifies the 93 // watch that fired, |child| indicates what has changed, and is relative to 94 // the currently watched path for |fired_watch|. The flag |created| is true if 95 // the object appears. 96 void OnFilePathChanged(InotifyReader::Watch fired_watch, 97 const FilePath::StringType& child, 98 bool created); 99 100 protected: 101 virtual ~FilePathWatcherImpl() {} 102 103 private: 104 // Start watching |path| for changes and notify |delegate| on each change. 105 // Returns true if watch for |path| has been added successfully. 106 virtual bool Watch(const FilePath& path, 107 bool recursive, 108 const FilePathWatcher::Callback& callback) OVERRIDE; 109 110 // Cancel the watch. This unregisters the instance with InotifyReader. 111 virtual void Cancel() OVERRIDE; 112 113 // Cleans up and stops observing the message_loop() thread. 114 virtual void CancelOnMessageLoopThread() OVERRIDE; 115 116 // Deletion of the FilePathWatcher will call Cancel() to dispose of this 117 // object in the right thread. This also observes destruction of the required 118 // cleanup thread, in case it quits before Cancel() is called. 119 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; 120 121 // Inotify watches are installed for all directory components of |target_|. A 122 // WatchEntry instance holds the watch descriptor for a component and the 123 // subdirectory for that identifies the next component. If a symbolic link 124 // is being watched, the target of the link is also kept. 125 struct WatchEntry { 126 explicit WatchEntry(const FilePath::StringType& dirname) 127 : watch(InotifyReader::kInvalidWatch), 128 subdir(dirname) {} 129 130 InotifyReader::Watch watch; 131 FilePath::StringType subdir; 132 FilePath::StringType linkname; 133 }; 134 typedef std::vector<WatchEntry> WatchVector; 135 136 // Reconfigure to watch for the most specific parent directory of |target_| 137 // that exists. Updates |watched_path_|. Returns true on success. 138 bool UpdateWatches() WARN_UNUSED_RESULT; 139 140 // |path| is a symlink to a non-existent target. Attempt to add a watch to 141 // the link target's parent directory. Returns true and update |watch_entry| 142 // on success. 143 bool AddWatchForBrokenSymlink(const FilePath& path, WatchEntry* watch_entry); 144 145 bool HasValidWatchVector() const; 146 147 // Callback to notify upon changes. 148 FilePathWatcher::Callback callback_; 149 150 // The file or directory we're supposed to watch. 151 FilePath target_; 152 153 // The vector of watches and next component names for all path components, 154 // starting at the root directory. The last entry corresponds to the watch for 155 // |target_| and always stores an empty next component name in |subdir|. 156 WatchVector watches_; 157 158 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); 159}; 160 161void InotifyReaderCallback(InotifyReader* reader, int inotify_fd, 162 int shutdown_fd) { 163 // Make sure the file descriptors are good for use with select(). 164 CHECK_LE(0, inotify_fd); 165 CHECK_GT(FD_SETSIZE, inotify_fd); 166 CHECK_LE(0, shutdown_fd); 167 CHECK_GT(FD_SETSIZE, shutdown_fd); 168 169 debug::TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop(); 170 171 while (true) { 172 fd_set rfds; 173 FD_ZERO(&rfds); 174 FD_SET(inotify_fd, &rfds); 175 FD_SET(shutdown_fd, &rfds); 176 177 // Wait until some inotify events are available. 178 int select_result = 179 HANDLE_EINTR(select(std::max(inotify_fd, shutdown_fd) + 1, 180 &rfds, NULL, NULL, NULL)); 181 if (select_result < 0) { 182 DPLOG(WARNING) << "select failed"; 183 return; 184 } 185 186 if (FD_ISSET(shutdown_fd, &rfds)) 187 return; 188 189 // Adjust buffer size to current event queue size. 190 int buffer_size; 191 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd, FIONREAD, 192 &buffer_size)); 193 194 if (ioctl_result != 0) { 195 DPLOG(WARNING) << "ioctl failed"; 196 return; 197 } 198 199 std::vector<char> buffer(buffer_size); 200 201 ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd, &buffer[0], 202 buffer_size)); 203 204 if (bytes_read < 0) { 205 DPLOG(WARNING) << "read from inotify fd failed"; 206 return; 207 } 208 209 ssize_t i = 0; 210 while (i < bytes_read) { 211 inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]); 212 size_t event_size = sizeof(inotify_event) + event->len; 213 DCHECK(i + event_size <= static_cast<size_t>(bytes_read)); 214 reader->OnInotifyEvent(event); 215 i += event_size; 216 } 217 } 218} 219 220static LazyInstance<InotifyReader>::Leaky g_inotify_reader = 221 LAZY_INSTANCE_INITIALIZER; 222 223InotifyReader::InotifyReader() 224 : thread_("inotify_reader"), 225 inotify_fd_(inotify_init()), 226 valid_(false) { 227 if (inotify_fd_ < 0) 228 PLOG(ERROR) << "inotify_init() failed"; 229 230 shutdown_pipe_[0] = -1; 231 shutdown_pipe_[1] = -1; 232 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) { 233 thread_.message_loop()->PostTask( 234 FROM_HERE, 235 Bind(&InotifyReaderCallback, this, inotify_fd_, shutdown_pipe_[0])); 236 valid_ = true; 237 } 238} 239 240InotifyReader::~InotifyReader() { 241 if (valid_) { 242 // Write to the self-pipe so that the select call in InotifyReaderTask 243 // returns. 244 ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1)); 245 DPCHECK(ret > 0); 246 DCHECK_EQ(ret, 1); 247 thread_.Stop(); 248 } 249 if (inotify_fd_ >= 0) 250 close(inotify_fd_); 251 if (shutdown_pipe_[0] >= 0) 252 close(shutdown_pipe_[0]); 253 if (shutdown_pipe_[1] >= 0) 254 close(shutdown_pipe_[1]); 255} 256 257InotifyReader::Watch InotifyReader::AddWatch( 258 const FilePath& path, FilePathWatcherImpl* watcher) { 259 if (!valid_) 260 return kInvalidWatch; 261 262 AutoLock auto_lock(lock_); 263 264 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), 265 IN_CREATE | IN_DELETE | 266 IN_CLOSE_WRITE | IN_MOVE | 267 IN_ONLYDIR); 268 269 if (watch == kInvalidWatch) 270 return kInvalidWatch; 271 272 watchers_[watch].insert(watcher); 273 274 return watch; 275} 276 277void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) { 278 if (!valid_ || (watch == kInvalidWatch)) 279 return; 280 281 AutoLock auto_lock(lock_); 282 283 watchers_[watch].erase(watcher); 284 285 if (watchers_[watch].empty()) { 286 watchers_.erase(watch); 287 inotify_rm_watch(inotify_fd_, watch); 288 } 289} 290 291void InotifyReader::OnInotifyEvent(const inotify_event* event) { 292 if (event->mask & IN_IGNORED) 293 return; 294 295 FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); 296 AutoLock auto_lock(lock_); 297 298 for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); 299 watcher != watchers_[event->wd].end(); 300 ++watcher) { 301 (*watcher)->OnFilePathChanged(event->wd, 302 child, 303 event->mask & (IN_CREATE | IN_MOVED_TO)); 304 } 305} 306 307FilePathWatcherImpl::FilePathWatcherImpl() { 308} 309 310void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch, 311 const FilePath::StringType& child, 312 bool created) { 313 if (!message_loop()->BelongsToCurrentThread()) { 314 // Switch to message_loop() to access |watches_| safely. 315 message_loop()->PostTask( 316 FROM_HERE, 317 Bind(&FilePathWatcherImpl::OnFilePathChanged, this, 318 fired_watch, child, created)); 319 return; 320 } 321 322 // Check to see if CancelOnMessageLoopThread() has already been called. 323 // May happen when code flow reaches here from the PostTask() above. 324 if (watches_.empty()) { 325 DCHECK(target_.empty()); 326 return; 327 } 328 329 DCHECK(MessageLoopForIO::current()); 330 DCHECK(HasValidWatchVector()); 331 332 // Find the entry in |watches_| that corresponds to |fired_watch|. 333 for (size_t i = 0; i < watches_.size(); ++i) { 334 const WatchEntry& watch_entry = watches_[i]; 335 if (fired_watch != watch_entry.watch) 336 continue; 337 338 // Check whether a path component of |target_| changed. 339 bool change_on_target_path = 340 child.empty() || 341 (child == watch_entry.linkname) || 342 (child == watch_entry.subdir); 343 344 // Check if the change references |target_| or a direct child of |target_|. 345 bool is_watch_for_target = watch_entry.subdir.empty(); 346 bool target_changed = 347 (is_watch_for_target && (child == watch_entry.linkname)) || 348 (is_watch_for_target && watch_entry.linkname.empty()) || 349 (watch_entry.subdir == child && watches_[i + 1].subdir.empty()); 350 351 // Update watches if a directory component of the |target_| path 352 // (dis)appears. Note that we don't add the additional restriction of 353 // checking the event mask to see if it is for a directory here as changes 354 // to symlinks on the target path will not have IN_ISDIR set in the event 355 // masks. As a result we may sometimes call UpdateWatches() unnecessarily. 356 if (change_on_target_path && !UpdateWatches()) { 357 callback_.Run(target_, true /* error */); 358 return; 359 } 360 361 // Report the following events: 362 // - The target or a direct child of the target got changed (in case the 363 // watched path refers to a directory). 364 // - One of the parent directories got moved or deleted, since the target 365 // disappears in this case. 366 // - One of the parent directories appears. The event corresponding to 367 // the target appearing might have been missed in this case, so recheck. 368 if (target_changed || 369 (change_on_target_path && !created) || 370 (change_on_target_path && PathExists(target_))) { 371 callback_.Run(target_, false /* error */); 372 return; 373 } 374 } 375} 376 377bool FilePathWatcherImpl::Watch(const FilePath& path, 378 bool recursive, 379 const FilePathWatcher::Callback& callback) { 380 DCHECK(target_.empty()); 381 DCHECK(MessageLoopForIO::current()); 382 if (recursive) { 383 // Recursive watch is not supported on this platform. 384 NOTIMPLEMENTED(); 385 return false; 386 } 387 388 set_message_loop(MessageLoopProxy::current().get()); 389 callback_ = callback; 390 target_ = path; 391 MessageLoop::current()->AddDestructionObserver(this); 392 393 std::vector<FilePath::StringType> comps; 394 target_.GetComponents(&comps); 395 DCHECK(!comps.empty()); 396 for (size_t i = 1; i < comps.size(); ++i) 397 watches_.push_back(WatchEntry(comps[i])); 398 watches_.push_back(WatchEntry(FilePath::StringType())); 399 return UpdateWatches(); 400} 401 402void FilePathWatcherImpl::Cancel() { 403 if (callback_.is_null()) { 404 // Watch was never called, or the message_loop() thread is already gone. 405 set_cancelled(); 406 return; 407 } 408 409 // Switch to the message_loop() if necessary so we can access |watches_|. 410 if (!message_loop()->BelongsToCurrentThread()) { 411 message_loop()->PostTask(FROM_HERE, 412 Bind(&FilePathWatcher::CancelWatch, 413 make_scoped_refptr(this))); 414 } else { 415 CancelOnMessageLoopThread(); 416 } 417} 418 419void FilePathWatcherImpl::CancelOnMessageLoopThread() { 420 set_cancelled(); 421 422 if (!callback_.is_null()) { 423 MessageLoop::current()->RemoveDestructionObserver(this); 424 callback_.Reset(); 425 } 426 427 for (size_t i = 0; i < watches_.size(); ++i) 428 g_inotify_reader.Get().RemoveWatch(watches_[i].watch, this); 429 watches_.clear(); 430 target_.clear(); 431} 432 433void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { 434 CancelOnMessageLoopThread(); 435} 436 437bool FilePathWatcherImpl::UpdateWatches() { 438 // Ensure this runs on the message_loop() exclusively in order to avoid 439 // concurrency issues. 440 DCHECK(message_loop()->BelongsToCurrentThread()); 441 DCHECK(HasValidWatchVector()); 442 443 // Walk the list of watches and update them as we go. 444 FilePath path(FILE_PATH_LITERAL("/")); 445 bool path_valid = true; 446 for (size_t i = 0; i < watches_.size(); ++i) { 447 WatchEntry& watch_entry = watches_[i]; 448 InotifyReader::Watch old_watch = watch_entry.watch; 449 watch_entry.watch = InotifyReader::kInvalidWatch; 450 watch_entry.linkname.clear(); 451 if (path_valid) { 452 watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this); 453 if (watch_entry.watch == InotifyReader::kInvalidWatch) { 454 if (IsLink(path)) { 455 path_valid = AddWatchForBrokenSymlink(path, &watch_entry); 456 } else { 457 path_valid = false; 458 } 459 } 460 } 461 if (old_watch != watch_entry.watch) 462 g_inotify_reader.Get().RemoveWatch(old_watch, this); 463 path = path.Append(watch_entry.subdir); 464 } 465 466 return true; 467} 468 469bool FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path, 470 WatchEntry* watch_entry) { 471 DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch); 472 FilePath link; 473 if (!ReadSymbolicLink(path, &link)) 474 return false; 475 476 if (!link.IsAbsolute()) 477 link = path.DirName().Append(link); 478 479 // Try watching symlink target directory. If the link target is "/", then we 480 // shouldn't get here in normal situations and if we do, we'd watch "/" for 481 // changes to a component "/" which is harmless so no special treatment of 482 // this case is required. 483 InotifyReader::Watch watch = 484 g_inotify_reader.Get().AddWatch(link.DirName(), this); 485 if (watch == InotifyReader::kInvalidWatch) { 486 // TODO(craig) Symlinks only work if the parent directory for the target 487 // exist. Ideally we should make sure we've watched all the components of 488 // the symlink path for changes. See crbug.com/91561 for details. 489 DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); 490 return false; 491 } 492 watch_entry->watch = watch; 493 watch_entry->linkname = link.BaseName().value(); 494 return true; 495} 496 497bool FilePathWatcherImpl::HasValidWatchVector() const { 498 if (watches_.empty()) 499 return false; 500 for (size_t i = 0; i < watches_.size() - 1; ++i) { 501 if (watches_[i].subdir.empty()) 502 return false; 503 } 504 return watches_[watches_.size() - 1].subdir.empty(); 505} 506 507} // namespace 508 509FilePathWatcher::FilePathWatcher() { 510 impl_ = new FilePathWatcherImpl(); 511} 512 513} // namespace base 514