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.
5 #include "base/files/file_path_watcher.h"
9 #include <sys/inotify.h>
10 #include <sys/ioctl.h>
11 #include <sys/select.h>
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"
38 class FilePathWatcherImpl;
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
45 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
46 static const Watch kInvalidWatch = -1;
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);
52 // Remove |watch| if it's valid.
53 void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
55 // Callback for InotifyReaderTask.
56 void OnInotifyEvent(const inotify_event* event);
59 friend struct DefaultLazyInstanceTraits<InotifyReader>;
61 typedef std::set<FilePathWatcherImpl*> WatcherSet;
66 // We keep track of which delegates want to be notified on which watches.
67 hash_map<Watch, WatcherSet> watchers_;
69 // Lock to protect watchers_.
72 // Separate thread on which we run blocking read for inotify events.
75 // File descriptor returned by inotify_init.
76 const int inotify_fd_;
78 // Use self-pipe trick to unblock select during shutdown.
79 int shutdown_pipe_[2];
81 // Flag set to true when startup was successful.
84 DISALLOW_COPY_AND_ASSIGN(InotifyReader);
87 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
88 public MessageLoop::DestructionObserver {
90 FilePathWatcherImpl();
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,
101 virtual ~FilePathWatcherImpl() {}
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,
108 const FilePathWatcher::Callback& callback) OVERRIDE;
110 // Cancel the watch. This unregisters the instance with InotifyReader.
111 virtual void Cancel() OVERRIDE;
113 // Cleans up and stops observing the message_loop() thread.
114 virtual void CancelOnMessageLoopThread() OVERRIDE;
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;
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.
126 explicit WatchEntry(const FilePath::StringType& dirname)
127 : watch(InotifyReader::kInvalidWatch),
130 InotifyReader::Watch watch;
131 FilePath::StringType subdir;
132 FilePath::StringType linkname;
134 typedef std::vector<WatchEntry> WatchVector;
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;
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|
143 bool AddWatchForBrokenSymlink(const FilePath& path, WatchEntry* watch_entry);
145 bool HasValidWatchVector() const;
147 // Callback to notify upon changes.
148 FilePathWatcher::Callback callback_;
150 // The file or directory we're supposed to watch.
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_;
158 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
161 void InotifyReaderCallback(InotifyReader* reader, int inotify_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);
169 debug::TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop();
174 FD_SET(inotify_fd, &rfds);
175 FD_SET(shutdown_fd, &rfds);
177 // Wait until some inotify events are available.
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";
186 if (FD_ISSET(shutdown_fd, &rfds))
189 // Adjust buffer size to current event queue size.
191 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd, FIONREAD,
194 if (ioctl_result != 0) {
195 DPLOG(WARNING) << "ioctl failed";
199 std::vector<char> buffer(buffer_size);
201 ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd, &buffer[0],
204 if (bytes_read < 0) {
205 DPLOG(WARNING) << "read from inotify fd failed";
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);
220 static LazyInstance<InotifyReader>::Leaky g_inotify_reader =
221 LAZY_INSTANCE_INITIALIZER;
223 InotifyReader::InotifyReader()
224 : thread_("inotify_reader"),
225 inotify_fd_(inotify_init()),
228 PLOG(ERROR) << "inotify_init() failed";
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(
235 Bind(&InotifyReaderCallback, this, inotify_fd_, shutdown_pipe_[0]));
240 InotifyReader::~InotifyReader() {
242 // Write to the self-pipe so that the select call in InotifyReaderTask
244 ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
249 if (inotify_fd_ >= 0)
251 if (shutdown_pipe_[0] >= 0)
252 close(shutdown_pipe_[0]);
253 if (shutdown_pipe_[1] >= 0)
254 close(shutdown_pipe_[1]);
257 InotifyReader::Watch InotifyReader::AddWatch(
258 const FilePath& path, FilePathWatcherImpl* watcher) {
260 return kInvalidWatch;
262 AutoLock auto_lock(lock_);
264 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
265 IN_CREATE | IN_DELETE |
266 IN_CLOSE_WRITE | IN_MOVE |
269 if (watch == kInvalidWatch)
270 return kInvalidWatch;
272 watchers_[watch].insert(watcher);
277 void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) {
278 if (!valid_ || (watch == kInvalidWatch))
281 AutoLock auto_lock(lock_);
283 watchers_[watch].erase(watcher);
285 if (watchers_[watch].empty()) {
286 watchers_.erase(watch);
287 inotify_rm_watch(inotify_fd_, watch);
291 void InotifyReader::OnInotifyEvent(const inotify_event* event) {
292 if (event->mask & IN_IGNORED)
295 FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL(""));
296 AutoLock auto_lock(lock_);
298 for (WatcherSet::iterator watcher = watchers_[event->wd].begin();
299 watcher != watchers_[event->wd].end();
301 (*watcher)->OnFilePathChanged(event->wd,
303 event->mask & (IN_CREATE | IN_MOVED_TO));
307 FilePathWatcherImpl::FilePathWatcherImpl() {
310 void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch,
311 const FilePath::StringType& child,
313 if (!message_loop()->BelongsToCurrentThread()) {
314 // Switch to message_loop() to access |watches_| safely.
315 message_loop()->PostTask(
317 Bind(&FilePathWatcherImpl::OnFilePathChanged, this,
318 fired_watch, child, created));
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());
329 DCHECK(MessageLoopForIO::current());
330 DCHECK(HasValidWatchVector());
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)
338 // Check whether a path component of |target_| changed.
339 bool change_on_target_path =
341 (child == watch_entry.linkname) ||
342 (child == watch_entry.subdir);
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());
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 */);
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 */);
377 bool FilePathWatcherImpl::Watch(const FilePath& path,
379 const FilePathWatcher::Callback& callback) {
380 DCHECK(target_.empty());
381 DCHECK(MessageLoopForIO::current());
383 // Recursive watch is not supported on this platform.
388 set_message_loop(MessageLoopProxy::current().get());
389 callback_ = callback;
391 MessageLoop::current()->AddDestructionObserver(this);
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();
402 void FilePathWatcherImpl::Cancel() {
403 if (callback_.is_null()) {
404 // Watch was never called, or the message_loop() thread is already gone.
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)));
415 CancelOnMessageLoopThread();
419 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
422 if (!callback_.is_null()) {
423 MessageLoop::current()->RemoveDestructionObserver(this);
427 for (size_t i = 0; i < watches_.size(); ++i)
428 g_inotify_reader.Get().RemoveWatch(watches_[i].watch, this);
433 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
434 CancelOnMessageLoopThread();
437 bool 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());
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();
452 watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this);
453 if (watch_entry.watch == InotifyReader::kInvalidWatch) {
455 path_valid = AddWatchForBrokenSymlink(path, &watch_entry);
461 if (old_watch != watch_entry.watch)
462 g_inotify_reader.Get().RemoveWatch(old_watch, this);
463 path = path.Append(watch_entry.subdir);
469 bool FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path,
470 WatchEntry* watch_entry) {
471 DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch);
473 if (!ReadSymbolicLink(path, &link))
476 if (!link.IsAbsolute())
477 link = path.DirName().Append(link);
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();
492 watch_entry->watch = watch;
493 watch_entry->linkname = link.BaseName().value();
497 bool FilePathWatcherImpl::HasValidWatchVector() const {
498 if (watches_.empty())
500 for (size_t i = 0; i < watches_.size() - 1; ++i) {
501 if (watches_[i].subdir.empty())
504 return watches_[watches_.size() - 1].subdir.empty();
509 FilePathWatcher::FilePathWatcher() {
510 impl_ = new FilePathWatcherImpl();