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

Side by Side Diff: base/directory_watcher_inotify.cc

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

Powered by Google App Engine
This is Rietveld 408576698