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