• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2009 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/directory_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/eintr_wrapper.h"
20 #include "base/file_path.h"
21 #include "base/file_util.h"
22 #include "base/hash_tables.h"
23 #include "base/lock.h"
24 #include "base/logging.h"
25 #include "base/message_loop.h"
26 #include "base/scoped_ptr.h"
27 #include "base/singleton.h"
28 #include "base/task.h"
29 #include "base/thread.h"
30 #include "base/waitable_event.h"
31 
32 namespace {
33 
34 class DirectoryWatcherImpl;
35 
36 // Singleton to manage all inotify watches.
37 class InotifyReader {
38  public:
39   typedef int Watch;  // Watch descriptor used by AddWatch and RemoveWatch.
40   static const Watch kInvalidWatch = -1;
41 
42   // Watch |path| for changes. |watcher| will be notified on each change.
43   // Returns kInvalidWatch on failure.
44   Watch AddWatch(const FilePath& path, DirectoryWatcherImpl* watcher);
45 
46   // Remove |watch|. Returns true on success.
47   bool RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher);
48 
49   // Callback for InotifyReaderTask.
50   void OnInotifyEvent(const inotify_event* event);
51 
52  private:
53   friend struct DefaultSingletonTraits<InotifyReader>;
54 
55   typedef std::set<DirectoryWatcherImpl*> WatcherSet;
56 
57   InotifyReader();
58   ~InotifyReader();
59 
60   // We keep track of which delegates want to be notified on which watches.
61   base::hash_map<Watch, WatcherSet> watchers_;
62 
63   // For each watch we also want to know the path it's watching.
64   base::hash_map<Watch, FilePath> paths_;
65 
66   // Lock to protect delegates_ and paths_.
67   Lock lock_;
68 
69   // Separate thread on which we run blocking read for inotify events.
70   base::Thread thread_;
71 
72   // File descriptor returned by inotify_init.
73   const int inotify_fd_;
74 
75   // Use self-pipe trick to unblock select during shutdown.
76   int shutdown_pipe_[2];
77 
78   // Flag set to true when startup was successful.
79   bool valid_;
80 
81   DISALLOW_COPY_AND_ASSIGN(InotifyReader);
82 };
83 
84 class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
85  public:
86   typedef std::set<FilePath> FilePathSet;
87 
88   DirectoryWatcherImpl();
89   ~DirectoryWatcherImpl();
90 
91   void EnsureSetupFinished();
92 
93   // Called for each event coming from one of watches.
94   void OnInotifyEvent(const inotify_event* event);
95 
96   // Callback for RegisterSubtreeWatchesTask.
97   bool OnEnumeratedSubtree(const FilePathSet& paths);
98 
99   // Start watching |path| for changes and notify |delegate| on each change.
100   // If |recursive| is true, watch entire subtree.
101   // Returns true if watch for |path| has been added successfully. Watches
102   // required for |recursive| are added on a background thread and have no
103   // effect on the return value.
104   virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
105                      MessageLoop* backend_loop, bool recursive);
106 
107  private:
108   typedef std::set<InotifyReader::Watch> WatchSet;
109   typedef std::set<ino_t> InodeSet;
110 
111   // Returns true if |inode| is watched by DirectoryWatcherImpl.
112   bool IsInodeWatched(ino_t inode) const;
113 
114   // Delegate to notify upon changes.
115   DirectoryWatcher::Delegate* delegate_;
116 
117   // Path we're watching (passed to delegate).
118   FilePath root_path_;
119 
120   // Watch returned by InotifyReader.
121   InotifyReader::Watch watch_;
122 
123   // Set of watched inodes.
124   InodeSet inodes_watched_;
125 
126   // Keep track of registered watches.
127   WatchSet watches_;
128 
129   // Lock to protect inodes_watched_ and watches_.
130   Lock lock_;
131 
132   // Flag set to true when recursively watching subtree.
133   bool recursive_;
134 
135   // Loop where we post directory change notifications to.
136   MessageLoop* loop_;
137 
138   // Event signaled when the background task finished adding initial inotify
139   // watches for recursive watch.
140   base::WaitableEvent recursive_setup_finished_;
141 
142   DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
143 };
144 
145 class RegisterSubtreeWatchesTask : public Task {
146  public:
RegisterSubtreeWatchesTask(DirectoryWatcherImpl * watcher,const FilePath & path)147   RegisterSubtreeWatchesTask(DirectoryWatcherImpl* watcher,
148                              const FilePath& path)
149       : watcher_(watcher),
150         path_(path) {
151   }
152 
Run()153   virtual void Run() {
154     file_util::FileEnumerator dir_list(path_, true,
155         file_util::FileEnumerator::DIRECTORIES);
156 
157     DirectoryWatcherImpl::FilePathSet subtree;
158     for (FilePath subdirectory = dir_list.Next();
159          !subdirectory.empty();
160          subdirectory = dir_list.Next()) {
161       subtree.insert(subdirectory);
162     }
163     watcher_->OnEnumeratedSubtree(subtree);
164   }
165 
166  private:
167   DirectoryWatcherImpl* watcher_;
168   FilePath path_;
169 
170   DISALLOW_COPY_AND_ASSIGN(RegisterSubtreeWatchesTask);
171 };
172 
173 class DirectoryWatcherImplNotifyTask : public Task {
174  public:
DirectoryWatcherImplNotifyTask(DirectoryWatcher::Delegate * delegate,const FilePath & path)175   DirectoryWatcherImplNotifyTask(DirectoryWatcher::Delegate* delegate,
176                                  const FilePath& path)
177       : delegate_(delegate),
178         path_(path) {
179   }
180 
Run()181   virtual void Run() {
182     delegate_->OnDirectoryChanged(path_);
183   }
184 
185  private:
186   DirectoryWatcher::Delegate* delegate_;
187   FilePath path_;
188 
189   DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplNotifyTask);
190 };
191 
192 class InotifyReaderTask : public Task {
193  public:
InotifyReaderTask(InotifyReader * reader,int inotify_fd,int shutdown_fd)194   InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
195       : reader_(reader),
196         inotify_fd_(inotify_fd),
197         shutdown_fd_(shutdown_fd) {
198   }
199 
Run()200   virtual void Run() {
201     while (true) {
202       fd_set rfds;
203       FD_ZERO(&rfds);
204       FD_SET(inotify_fd_, &rfds);
205       FD_SET(shutdown_fd_, &rfds);
206 
207       // Wait until some inotify events are available.
208       int select_result =
209         HANDLE_EINTR(select(std::max(inotify_fd_, shutdown_fd_) + 1,
210                             &rfds, NULL, NULL, NULL));
211       if (select_result < 0) {
212         DPLOG(WARNING) << "select failed";
213         return;
214       }
215 
216       if (FD_ISSET(shutdown_fd_, &rfds))
217         return;
218 
219       // Adjust buffer size to current event queue size.
220       int buffer_size;
221       int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD,
222                                             &buffer_size));
223 
224       if (ioctl_result != 0) {
225         DPLOG(WARNING) << "ioctl failed";
226         return;
227       }
228 
229       std::vector<char> buffer(buffer_size);
230 
231       ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd_, &buffer[0],
232                                              buffer_size));
233 
234       if (bytes_read < 0) {
235         DPLOG(WARNING) << "read from inotify fd failed";
236         return;
237       }
238 
239       ssize_t i = 0;
240       while (i < bytes_read) {
241         inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
242         size_t event_size = sizeof(inotify_event) + event->len;
243         DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
244         reader_->OnInotifyEvent(event);
245         i += event_size;
246       }
247     }
248   }
249 
250  private:
251   InotifyReader* reader_;
252   int inotify_fd_;
253   int shutdown_fd_;
254 
255   DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
256 };
257 
InotifyReader()258 InotifyReader::InotifyReader()
259     : thread_("inotify_reader"),
260       inotify_fd_(inotify_init()),
261       valid_(false) {
262   shutdown_pipe_[0] = -1;
263   shutdown_pipe_[1] = -1;
264   if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
265     thread_.message_loop()->PostTask(
266         FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
267     valid_ = true;
268   }
269 }
270 
~InotifyReader()271 InotifyReader::~InotifyReader() {
272   if (valid_) {
273     // Write to the self-pipe so that the select call in InotifyReaderTask
274     // returns.
275     ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
276     DPCHECK(ret > 0);
277     DCHECK_EQ(ret, 1);
278     thread_.Stop();
279   }
280   if (inotify_fd_ >= 0)
281     close(inotify_fd_);
282   if (shutdown_pipe_[0] >= 0)
283     close(shutdown_pipe_[0]);
284   if (shutdown_pipe_[1] >= 0)
285     close(shutdown_pipe_[1]);
286 }
287 
AddWatch(const FilePath & path,DirectoryWatcherImpl * watcher)288 InotifyReader::Watch InotifyReader::AddWatch(
289     const FilePath& path, DirectoryWatcherImpl* watcher) {
290 
291   if (!valid_)
292     return kInvalidWatch;
293 
294   AutoLock auto_lock(lock_);
295 
296   Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
297                                   IN_CREATE | IN_DELETE |
298                                   IN_CLOSE_WRITE | IN_MOVE);
299 
300   if (watch == kInvalidWatch)
301     return kInvalidWatch;
302 
303   if (paths_[watch].empty())
304     paths_[watch] = path;  // We don't yet watch this path.
305 
306   watchers_[watch].insert(watcher);
307 
308   return watch;
309 }
310 
RemoveWatch(Watch watch,DirectoryWatcherImpl * watcher)311 bool InotifyReader::RemoveWatch(Watch watch,
312                                 DirectoryWatcherImpl* watcher) {
313   if (!valid_)
314     return false;
315 
316   AutoLock auto_lock(lock_);
317 
318   if (paths_[watch].empty())
319     return false;  // We don't recognize this watch.
320 
321   watchers_[watch].erase(watcher);
322 
323   if (watchers_[watch].empty()) {
324     paths_.erase(watch);
325     watchers_.erase(watch);
326     return (inotify_rm_watch(inotify_fd_, watch) == 0);
327   }
328 
329   return true;
330 }
331 
OnInotifyEvent(const inotify_event * event)332 void InotifyReader::OnInotifyEvent(const inotify_event* event) {
333   if (event->mask & IN_IGNORED)
334     return;
335 
336   // In case you want to limit the scope of this lock, it's not sufficient
337   // to just copy things under the lock, and then run the notifications
338   // without holding the lock. DirectoryWatcherImpl's dtor removes its watches,
339   // and to do that obtains the lock. After it finishes removing watches,
340   // it's destroyed. So, if you copy under the lock and notify without the lock,
341   // it's possible you'll copy the DirectoryWatcherImpl which is being
342   // destroyed, then it will destroy itself, and then you'll try to notify it.
343   AutoLock auto_lock(lock_);
344 
345   for (WatcherSet::iterator watcher = watchers_[event->wd].begin();
346        watcher != watchers_[event->wd].end();
347        ++watcher) {
348     (*watcher)->OnInotifyEvent(event);
349   }
350 }
351 
DirectoryWatcherImpl()352 DirectoryWatcherImpl::DirectoryWatcherImpl()
353     : watch_(InotifyReader::kInvalidWatch),
354       recursive_setup_finished_(false, false) {
355 }
356 
~DirectoryWatcherImpl()357 DirectoryWatcherImpl::~DirectoryWatcherImpl() {
358   if (watch_ == InotifyReader::kInvalidWatch)
359     return;
360 
361   if (recursive_)
362     recursive_setup_finished_.Wait();
363   for (WatchSet::iterator watch = watches_.begin();
364        watch != watches_.end();
365        ++watch) {
366     Singleton<InotifyReader>::get()->RemoveWatch(*watch, this);
367   }
368   watches_.clear();
369   inodes_watched_.clear();
370 }
371 
OnInotifyEvent(const inotify_event * event)372 void DirectoryWatcherImpl::OnInotifyEvent(const inotify_event* event) {
373   loop_->PostTask(FROM_HERE,
374       new DirectoryWatcherImplNotifyTask(delegate_, root_path_));
375 
376   if (!(event->mask & IN_ISDIR))
377     return;
378 
379   if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
380     // TODO(phajdan.jr): add watch for this new directory.
381     NOTIMPLEMENTED();
382   } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
383     // TODO(phajdan.jr): remove our watch for this directory.
384     NOTIMPLEMENTED();
385   }
386 }
387 
IsInodeWatched(ino_t inode) const388 bool DirectoryWatcherImpl::IsInodeWatched(ino_t inode) const {
389   return inodes_watched_.find(inode) != inodes_watched_.end();
390 }
391 
OnEnumeratedSubtree(const FilePathSet & subtree)392 bool DirectoryWatcherImpl::OnEnumeratedSubtree(const FilePathSet& subtree) {
393   DCHECK(recursive_);
394 
395   if (watch_ == InotifyReader::kInvalidWatch) {
396     recursive_setup_finished_.Signal();
397     return false;
398   }
399 
400   bool success = true;
401 
402   {
403     // Limit the scope of auto_lock so it releases lock_ before we signal
404     // recursive_setup_finished_. Our dtor waits on recursive_setup_finished_
405     // and could otherwise destroy the lock before we release it.
406     AutoLock auto_lock(lock_);
407 
408     for (FilePathSet::iterator subdirectory = subtree.begin();
409          subdirectory != subtree.end();
410          ++subdirectory) {
411       ino_t inode;
412       if (!file_util::GetInode(*subdirectory, &inode)) {
413         success = false;
414         continue;
415       }
416       if (IsInodeWatched(inode))
417         continue;
418       InotifyReader::Watch watch =
419         Singleton<InotifyReader>::get()->AddWatch(*subdirectory, this);
420       if (watch != InotifyReader::kInvalidWatch) {
421         watches_.insert(watch);
422         inodes_watched_.insert(inode);
423       }
424     }
425   }
426 
427   recursive_setup_finished_.Signal();
428   return success;
429 }
430 
Watch(const FilePath & path,DirectoryWatcher::Delegate * delegate,MessageLoop * backend_loop,bool recursive)431 bool DirectoryWatcherImpl::Watch(const FilePath& path,
432                                  DirectoryWatcher::Delegate* delegate,
433                                  MessageLoop* backend_loop, bool recursive) {
434 
435   // Can only watch one path.
436   DCHECK(watch_ == InotifyReader::kInvalidWatch);
437 
438   ino_t inode;
439   if (!file_util::GetInode(path, &inode))
440     return false;
441 
442   delegate_ = delegate;
443   recursive_ = recursive;
444   root_path_ = path;
445   loop_ = MessageLoop::current();
446   watch_ = Singleton<InotifyReader>::get()->AddWatch(path, this);
447   if (watch_ == InotifyReader::kInvalidWatch)
448     return false;
449 
450   {
451     AutoLock auto_lock(lock_);
452     inodes_watched_.insert(inode);
453     watches_.insert(watch_);
454   }
455 
456   if (recursive_) {
457     Task* subtree_task = new RegisterSubtreeWatchesTask(this, root_path_);
458     if (backend_loop) {
459       backend_loop->PostTask(FROM_HERE, subtree_task);
460     } else {
461       subtree_task->Run();
462       delete subtree_task;
463     }
464   }
465 
466   return true;
467 }
468 
469 }  // namespace
470 
DirectoryWatcher()471 DirectoryWatcher::DirectoryWatcher() {
472   impl_ = new DirectoryWatcherImpl();
473 }
474