Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(125)

Side by Side Diff: base/directory_watcher_inotify.cc

Issue 92032: Add support for almost-recursive watches in Linux DirectoryWatcher (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: '' Created 11 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | base/directory_watcher_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/directory_watcher.h" 5 #include "base/directory_watcher.h"
6 6
7 #include <errno.h> 7 #include <errno.h>
8 #include <string.h> 8 #include <string.h>
9 #include <sys/inotify.h>
9 #include <sys/ioctl.h> 10 #include <sys/ioctl.h>
10 #include <sys/inotify.h>
11 #include <sys/select.h> 11 #include <sys/select.h>
12 #include <unistd.h> 12 #include <unistd.h>
13 13
14 #include <algorithm> 14 #include <algorithm>
15 #include <set> 15 #include <set>
16 #include <utility> 16 #include <utility>
17 #include <vector> 17 #include <vector>
18 18
19 #include "base/file_path.h" 19 #include "base/file_path.h"
20 #include "base/file_util.h"
20 #include "base/hash_tables.h" 21 #include "base/hash_tables.h"
21 #include "base/lock.h" 22 #include "base/lock.h"
22 #include "base/logging.h" 23 #include "base/logging.h"
23 #include "base/message_loop.h" 24 #include "base/message_loop.h"
24 #include "base/scoped_ptr.h" 25 #include "base/scoped_ptr.h"
25 #include "base/singleton.h" 26 #include "base/singleton.h"
26 #include "base/task.h" 27 #include "base/task.h"
27 #include "base/thread.h" 28 #include "base/thread.h"
28 29
29 namespace { 30 namespace {
30 31
32 class DirectoryWatcherImpl;
33
31 // Singleton to manage all inotify watches. 34 // Singleton to manage all inotify watches.
32 class InotifyReader { 35 class InotifyReader {
33 public: 36 public:
34 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch. 37 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
35 static const Watch kInvalidWatch = -1; 38 static const Watch kInvalidWatch = -1;
36 39
37 // Watch |path| for changes. |delegate| will be notified on each change. Does 40 // Watch |path| for changes. |watcher| will be notified on each change.
38 // not check for duplicates. If you call it n times with same |path|
39 // and |delegate|, it will receive n notifications for each change
40 // in |path|. It makes implementation of DirectoryWatcher simple.
41 // Returns kInvalidWatch on failure. 41 // Returns kInvalidWatch on failure.
42 Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate); 42 Watch AddWatch(const FilePath& path, DirectoryWatcherImpl* watcher);
43 43
44 // Remove |watch| for |delegate|. If you had n watches for same |delegate| 44 // Remove |watch|. Returns true on success.
45 // and path, after calling this function you will have n - 1. 45 bool RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher);
46 // Returns true on success.
47 bool RemoveWatch(Watch watch, DirectoryWatcher::Delegate* delegate);
48 46
49 // Callback for InotifyReaderTask. 47 // Callback for InotifyReaderTask.
50 void OnInotifyEvent(inotify_event* event); 48 void OnInotifyEvent(inotify_event* event);
51 49
52 private: 50 private:
53 friend struct DefaultSingletonTraits<InotifyReader>; 51 friend struct DefaultSingletonTraits<InotifyReader>;
54 52
55 typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo; 53 typedef std::set<DirectoryWatcherImpl*> WatcherSet;
56 typedef std::multiset<DelegateInfo> DelegateSet;
57 54
58 InotifyReader(); 55 InotifyReader();
59 ~InotifyReader(); 56 ~InotifyReader();
60 57
61 // We keep track of which delegates want to be notified on which watches. 58 // We keep track of which delegates want to be notified on which watches.
62 // Multiset is used because there may be many DirectoryWatchers for same path 59 base::hash_map<Watch, WatcherSet> watchers_;
63 // and delegate.
64 base::hash_map<Watch, DelegateSet> delegates_;
65 60
66 // For each watch we also want to know the path it's watching. 61 // For each watch we also want to know the path it's watching.
67 base::hash_map<Watch, FilePath> paths_; 62 base::hash_map<Watch, FilePath> paths_;
68 63
69 // Lock to protect delegates_ and paths_. 64 // Lock to protect delegates_ and paths_.
70 Lock lock_; 65 Lock lock_;
71 66
72 // Separate thread on which we run blocking read for inotify events. 67 // Separate thread on which we run blocking read for inotify events.
73 base::Thread thread_; 68 base::Thread thread_;
74 69
75 // File descriptor returned by inotify_init. 70 // File descriptor returned by inotify_init.
76 const int inotify_fd_; 71 const int inotify_fd_;
77 72
78 // Use self-pipe trick to unblock select during shutdown. 73 // Use self-pipe trick to unblock select during shutdown.
79 int shutdown_pipe_[2]; 74 int shutdown_pipe_[2];
80 75
81 // Flag set to true when startup was successful. 76 // Flag set to true when startup was successful.
82 bool valid_; 77 bool valid_;
83 78
84 DISALLOW_COPY_AND_ASSIGN(InotifyReader); 79 DISALLOW_COPY_AND_ASSIGN(InotifyReader);
85 }; 80 };
86 81
82 class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
83 public:
84
85 typedef std::set<FilePath> FilePathSet;
86
87 DirectoryWatcherImpl();
88 ~DirectoryWatcherImpl();
89
90 // Called for each event coming from one of watches.
91 void HandleInotifyEvent(inotify_event* event);
92
93 // Callback for DirectoryWatcherImplEnumeratePathTask.
94 void OnEnumeratedPath(const std::set<FilePath>& subtree);
95
96 // Start watching |path| for changes and notify |delegate| on each change.
97 // Watch entire subtree, when |recursive| is true.
98 // Returns true if watch for |path| has been added successfuly. Watches
99 // required for |recursive| are added on a background thread and have no
100 // effect on the return value.
101 virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
102 bool recursive);
103
104 private:
105 typedef std::set<InotifyReader::Watch> WatchSet;
106
107 // Returns true if |inode| is watched by DirectoryWatcherImpl.
108 bool isPathWatched(ino_t inode) const;
109
110 // Return true if watching subtree is success.
111 bool watchSubtree(const FilePathSet& subtree);
112
113 // Lock to protect attributes.
114 Lock lock_;
115
116 // Delegate to notify upon changes.
117 DirectoryWatcher::Delegate* delegate_;
118
119 // Path we're watching (passed to delegate).
120 FilePath root_path_;
121
122 // Watch returned by InotifyReader.
123 InotifyReader::Watch root_watch_;
124
125 typedef std::set<ino_t> InodeSet;
126
127 // Hold inode for paths that are watched.
128 InodeSet path_watched_;
129
130 // We need to know who is watching what inode,
131 // for cleaning up and handling events.
132 base::hash_map<ino_t, DirectoryWatcherImpl*> path_watcher_;
133
134 // Flag set to true when recursively watching subtree.
135 bool recursive_;
136
137 // Flag set to true when thread startup was successful.
138 bool path_enumerator_thread_started_;
139
140 // Seperate thread on which we enumerate directory subtree.
141 base::Thread path_enumerator_thread_;
142
143 MessageLoop* loop_;
144
145 DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
146 };
147
148 class DirectoryWatcherImplEnumeratePathTask : public Task {
149 public:
150 DirectoryWatcherImplEnumeratePathTask(DirectoryWatcherImpl* watcher,
151 const FilePath& path)
152 :watcher_(watcher), path_(path) {}
153
154 virtual void Run() {
155 file_util::FileEnumerator dir_list(path_, true,
156 file_util::FileEnumerator::DIRECTORIES);
157
158 DirectoryWatcherImpl::FilePathSet subtree;
159 for (FilePath subdirectory = dir_list.Next();
160 !subdirectory.empty();
161 subdirectory = dir_list.Next()) {
162 subtree.insert(subdirectory);
163 }
164 watcher_->OnEnumeratedPath(subtree);
165 }
166
167 private:
168 DirectoryWatcherImpl* watcher_;
169 FilePath path_;
170
171 DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplEnumeratePathTask);
172 };
173
174 class DirectoryWatcherImplNotifyTask : public Task {
175 public:
176 DirectoryWatcherImplNotifyTask(DirectoryWatcher::Delegate* delegate,
177 const FilePath& path)
178 :delegate_(delegate), path_(path) {}
179
180 virtual void Run() {
181 delegate_->OnDirectoryChanged(path_);
182 }
183
184 private:
185 DirectoryWatcher::Delegate* delegate_;
186 FilePath path_;
187
188 DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplNotifyTask);
189 };
190
87 class InotifyReaderTask : public Task { 191 class InotifyReaderTask : public Task {
88 public: 192 public:
89 InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd) 193 InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
90 : reader_(reader), 194 : reader_(reader),
91 inotify_fd_(inotify_fd), 195 inotify_fd_(inotify_fd),
92 shutdown_fd_(shutdown_fd) { 196 shutdown_fd_(shutdown_fd) {
93 } 197 }
94 198
95 virtual void Run() { 199 virtual void Run() {
96 while (true) { 200 while (true) {
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
145 } 249 }
146 250
147 private: 251 private:
148 InotifyReader* reader_; 252 InotifyReader* reader_;
149 int inotify_fd_; 253 int inotify_fd_;
150 int shutdown_fd_; 254 int shutdown_fd_;
151 255
152 DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask); 256 DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
153 }; 257 };
154 258
155 class InotifyReaderNotifyTask : public Task {
156 public:
157 InotifyReaderNotifyTask(DirectoryWatcher::Delegate* delegate,
158 const FilePath& path)
159 : delegate_(delegate),
160 path_(path) {
161 }
162
163 virtual void Run() {
164 delegate_->OnDirectoryChanged(path_);
165 }
166
167 private:
168 DirectoryWatcher::Delegate* delegate_;
169 FilePath path_;
170
171 DISALLOW_COPY_AND_ASSIGN(InotifyReaderNotifyTask);
172 };
173
174 InotifyReader::InotifyReader() 259 InotifyReader::InotifyReader()
175 : thread_("inotify_reader"), 260 : thread_("inotify_reader"),
176 inotify_fd_(inotify_init()), 261 inotify_fd_(inotify_init()),
177 valid_(false) { 262 valid_(false) {
178 shutdown_pipe_[0] = -1; 263 shutdown_pipe_[0] = -1;
179 shutdown_pipe_[1] = -1; 264 shutdown_pipe_[1] = -1;
180 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) { 265 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
181 thread_.message_loop()->PostTask( 266 thread_.message_loop()->PostTask(
182 FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0])); 267 FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
183 valid_ = true; 268 valid_ = true;
(...skipping 14 matching lines...) Expand all
198 } 283 }
199 if (inotify_fd_ >= 0) 284 if (inotify_fd_ >= 0)
200 close(inotify_fd_); 285 close(inotify_fd_);
201 if (shutdown_pipe_[0] >= 0) 286 if (shutdown_pipe_[0] >= 0)
202 close(shutdown_pipe_[0]); 287 close(shutdown_pipe_[0]);
203 if (shutdown_pipe_[1] >= 0) 288 if (shutdown_pipe_[1] >= 0)
204 close(shutdown_pipe_[1]); 289 close(shutdown_pipe_[1]);
205 } 290 }
206 291
207 InotifyReader::Watch InotifyReader::AddWatch( 292 InotifyReader::Watch InotifyReader::AddWatch(
208 const FilePath& path, DirectoryWatcher::Delegate* delegate) { 293 const FilePath& path, DirectoryWatcherImpl* watcher) {
294
209 if (!valid_) 295 if (!valid_)
210 return kInvalidWatch; 296 return kInvalidWatch;
211 297
212 AutoLock auto_lock(lock_); 298 AutoLock auto_lock(lock_);
213 299
214 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), 300 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
215 IN_CREATE | IN_DELETE | 301 IN_CREATE | IN_DELETE |
216 IN_CLOSE_WRITE | IN_MOVE); 302 IN_CLOSE_WRITE | IN_MOVE);
303
217 if (watch == kInvalidWatch) 304 if (watch == kInvalidWatch)
218 return kInvalidWatch; 305 return kInvalidWatch;
219 306
220 if (paths_[watch].empty()) 307 if (paths_[watch].empty())
221 paths_[watch] = path; // We don't yet watch this path. 308 paths_[watch] = path; // We don't yet watch this path.
222 309
223 delegates_[watch].insert(std::make_pair(delegate, MessageLoop::current())); 310 watchers_[watch].insert(watcher);
224 311
225 return watch; 312 return watch;
226 } 313 }
227 314
228 bool InotifyReader::RemoveWatch(Watch watch, 315 bool InotifyReader::RemoveWatch(Watch watch,
229 DirectoryWatcher::Delegate* delegate) { 316 DirectoryWatcherImpl* watcher) {
230 if (!valid_) 317 if (!valid_)
231 return false; 318 return false;
232 319
233 AutoLock auto_lock(lock_); 320 AutoLock auto_lock(lock_);
234 321
235 if (paths_[watch].empty()) 322 if (paths_[watch].empty())
236 return false; // We don't recognize this watch. 323 return false; // We don't recognize this watch.
237 324
238 // Only erase one occurrence of delegate (there may be more). 325 watchers_[watch].erase(watcher);
239 delegates_[watch].erase(
240 delegates_[watch].find(std::make_pair(delegate, MessageLoop::current())));
241 326
242 if (delegates_[watch].empty()) { 327 if (watchers_[watch].empty()) {
243 paths_.erase(watch); 328 paths_.erase(watch);
244 delegates_.erase(watch); 329 watchers_.erase(watch);
245
246 return (inotify_rm_watch(inotify_fd_, watch) == 0); 330 return (inotify_rm_watch(inotify_fd_, watch) == 0);
247 } 331 }
248 332
249 return true; 333 return true;
250 } 334 }
251 335
252 void InotifyReader::OnInotifyEvent(inotify_event* event) { 336 void InotifyReader::OnInotifyEvent(inotify_event* event) {
253 if (event->mask & IN_IGNORED) 337 if (event->mask & IN_IGNORED)
254 return; 338 return;
255 339
256 DelegateSet delegates_to_notify; 340 WatcherSet watchers_to_notify;
257 FilePath changed_path; 341 FilePath changed_path;
258 342
259 { 343 {
260 AutoLock auto_lock(lock_); 344 AutoLock auto_lock(lock_);
261 changed_path = paths_[event->wd]; 345 changed_path = paths_[event->wd];
262 delegates_to_notify.insert(delegates_[event->wd].begin(), 346 watchers_to_notify.insert(watchers_[event->wd].begin(),
263 delegates_[event->wd].end()); 347 watchers_[event->wd].end());
264 } 348 }
265 349
266 DelegateSet::iterator i; 350 for (WatcherSet::iterator watcher = watchers_to_notify.begin();
267 for (i = delegates_to_notify.begin(); i != delegates_to_notify.end(); ++i) { 351 watcher != watchers_to_notify.end();
268 DirectoryWatcher::Delegate* delegate = i->first; 352 ++watcher) {
269 MessageLoop* loop = i->second; 353 (*watcher)->HandleInotifyEvent(event);
270 loop->PostTask(FROM_HERE,
271 new InotifyReaderNotifyTask(delegate, changed_path));
272 } 354 }
273 } 355 }
274 356
275 class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate { 357 DirectoryWatcherImpl::DirectoryWatcherImpl()
276 public: 358 : root_watch_(InotifyReader::kInvalidWatch),
277 DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {} 359 path_enumerator_thread_started_(false),
278 ~DirectoryWatcherImpl(); 360 path_enumerator_thread_("directory_enumerator"),
279 361 loop_(MessageLoop::current()) {}
280 virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
281 bool recursive);
282
283 private:
284 // Delegate to notify upon changes.
285 DirectoryWatcher::Delegate* delegate_;
286
287 // Path we're watching (passed to delegate).
288 FilePath path_;
289
290 // Watch returned by InotifyReader.
291 InotifyReader::Watch watch_;
292
293 DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
294 };
295 362
296 DirectoryWatcherImpl::~DirectoryWatcherImpl() { 363 DirectoryWatcherImpl::~DirectoryWatcherImpl() {
297 if (watch_ != InotifyReader::kInvalidWatch) 364 if (root_watch_ == InotifyReader::kInvalidWatch)
298 Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_); 365 return;
366 for (InodeSet::iterator i = path_watched_.begin();
367 i != path_watched_.end();
368 ++i) {
369 DirectoryWatcherImpl* delete_me = path_watcher_[*i];
370 if (Singleton<InotifyReader>::get()->RemoveWatch(delete_me->root_watch_,
371 delete_me)) {
372 delete_me->root_watch_ = InotifyReader::kInvalidWatch;
373 }
374 }
375 path_watcher_.clear();
376 path_watched_.clear();
377 if (path_enumerator_thread_started_)
378 path_enumerator_thread_.Stop();
379 }
380
381 void DirectoryWatcherImpl::HandleInotifyEvent(inotify_event* event) {
382 loop_->PostTask(FROM_HERE,
383 new DirectoryWatcherImplNotifyTask(delegate_, root_path_));
384
385 if (!(event->mask & IN_ISDIR))
386 return;
387
388 if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
389 NOTIMPLEMENTED();
390 } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
391 NOTIMPLEMENTED();
392 }
393 }
394
395 bool DirectoryWatcherImpl::isPathWatched(ino_t inode) const {
396 return path_watched_.find(inode) != path_watched_.end();
397 }
398
399 void DirectoryWatcherImpl::OnEnumeratedPath(const FilePathSet& subtree) {
400 DCHECK(recursive_);
401 watchSubtree(subtree);
299 } 402 }
300 403
301 bool DirectoryWatcherImpl::Watch(const FilePath& path, 404 bool DirectoryWatcherImpl::Watch(const FilePath& path,
302 DirectoryWatcher::Delegate* delegate, bool recursive) { 405 DirectoryWatcher::Delegate* delegate, bool recursive) {
303 DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path.
304 406
305 if (recursive) { 407 // Can only watch one path.
306 // TODO(phajdan.jr): Support recursive watches. 408 DCHECK(root_watch_ == InotifyReader::kInvalidWatch);
307 // Unfortunately inotify has no "native" support for them, but it can be 409
308 // emulated by walking the directory tree and setting up watches for each 410 ino_t inode;
309 // directory. Of course this is ineffective for large directory trees. 411 if (!file_util::GetInode(path, &inode))
310 // For the algorithm, see the link below:
311 // http://osdir.com/ml/gnome.dashboard.devel/2004-10/msg00022.html
312 NOTIMPLEMENTED();
313 return false; 412 return false;
413
414 InotifyReader::Watch watch =
415 Singleton<InotifyReader>::get()->AddWatch(path, this);
416 if (watch == InotifyReader::kInvalidWatch)
417 return false;
418
419 delegate_ = delegate;
420 recursive_ = recursive;
421 root_path_ = path;
422 root_watch_ = watch;
423
424 {
425 AutoLock auto_lock(lock_);
426 path_watched_.insert(inode);
427 path_watcher_[inode] = this;
314 } 428 }
315 429
316 delegate_ = delegate; 430 if (recursive_) {
317 path_ = path; 431 path_enumerator_thread_started_ = path_enumerator_thread_.Start();
318 watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_); 432 if (path_enumerator_thread_started_) {
433 path_enumerator_thread_.message_loop()->PostTask(
434 FROM_HERE,
435 new DirectoryWatcherImplEnumeratePathTask(this, root_path_));
436 }
437 }
319 438
320 return watch_ != InotifyReader::kInvalidWatch; 439 return true;
440 }
441
442 bool DirectoryWatcherImpl::watchSubtree(const FilePathSet& subtree) {
443 DCHECK(recursive_);
444
445 if (root_watch_ == InotifyReader::kInvalidWatch)
446 return false;
447
448 bool success = true;
449 AutoLock auto_lock(lock_);
450 for (FilePathSet::iterator subdirectory = subtree.begin();
451 subdirectory != subtree.end();
452 ++subdirectory) {
453 ino_t inode;
454 if (!file_util::GetInode(*subdirectory, &inode)) {
455 success = false;
456 continue;
457 }
458 if (isPathWatched(inode))
459 continue;
460 DirectoryWatcherImpl* child = new DirectoryWatcherImpl();
461 child->loop_ = loop_;
462 if (!child->Watch(*subdirectory, delegate_, false)) {
463 scoped_refptr<DirectoryWatcherImpl> delete_me = child;
464 } else {
465 path_watcher_[inode] = child;
466 path_watched_.insert(inode);
467 }
468 }
469 return success;
321 } 470 }
322 471
323 } // namespace 472 } // namespace
324 473
325 DirectoryWatcher::DirectoryWatcher() { 474 DirectoryWatcher::DirectoryWatcher() {
326 impl_ = new DirectoryWatcherImpl(); 475 impl_ = new DirectoryWatcherImpl();
327 } 476 }
OLDNEW
« no previous file with comments | « no previous file | base/directory_watcher_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698