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