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

Side by Side Diff: base/directory_watcher_inotify.cc

Issue 115229: Add support for almost-recursive watches in Linux DirectoryWatcher (Closed)
Patch Set: fix NULL file_thread scenario 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
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"
30 #include "base/waitable_event.h"
29 31
30 namespace { 32 namespace {
31 33
34 class DirectoryWatcherImpl;
35
32 // Singleton to manage all inotify watches. 36 // Singleton to manage all inotify watches.
33 class InotifyReader { 37 class InotifyReader {
34 public: 38 public:
35 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch. 39 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
36 static const Watch kInvalidWatch = -1; 40 static const Watch kInvalidWatch = -1;
37 41
38 // Watch |path| for changes. |delegate| will be notified on each change. Does 42 // 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. 43 // Returns kInvalidWatch on failure.
43 Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate); 44 Watch AddWatch(const FilePath& path, DirectoryWatcherImpl* watcher);
44 45
45 // Remove |watch| for |delegate|. If you had n watches for same |delegate| 46 // Remove |watch|. Returns true on success.
46 // and path, after calling this function you will have n - 1. 47 bool RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher);
47 // Returns true on success.
48 bool RemoveWatch(Watch watch, DirectoryWatcher::Delegate* delegate);
49 48
50 // Callback for InotifyReaderTask. 49 // Callback for InotifyReaderTask.
51 void OnInotifyEvent(inotify_event* event); 50 void OnInotifyEvent(const inotify_event* event);
52 51
53 private: 52 private:
54 friend struct DefaultSingletonTraits<InotifyReader>; 53 friend struct DefaultSingletonTraits<InotifyReader>;
55 54
56 typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo; 55 typedef std::set<DirectoryWatcherImpl*> WatcherSet;
57 typedef std::multiset<DelegateInfo> DelegateSet;
58 56
59 InotifyReader(); 57 InotifyReader();
60 ~InotifyReader(); 58 ~InotifyReader();
61 59
62 // We keep track of which delegates want to be notified on which watches. 60 // 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 61 base::hash_map<Watch, WatcherSet> watchers_;
64 // and delegate.
65 base::hash_map<Watch, DelegateSet> delegates_;
66 62
67 // For each watch we also want to know the path it's watching. 63 // For each watch we also want to know the path it's watching.
68 base::hash_map<Watch, FilePath> paths_; 64 base::hash_map<Watch, FilePath> paths_;
69 65
70 // Lock to protect delegates_ and paths_. 66 // Lock to protect delegates_ and paths_.
71 Lock lock_; 67 Lock lock_;
72 68
73 // Separate thread on which we run blocking read for inotify events. 69 // Separate thread on which we run blocking read for inotify events.
74 base::Thread thread_; 70 base::Thread thread_;
75 71
76 // File descriptor returned by inotify_init. 72 // File descriptor returned by inotify_init.
77 const int inotify_fd_; 73 const int inotify_fd_;
78 74
79 // Use self-pipe trick to unblock select during shutdown. 75 // Use self-pipe trick to unblock select during shutdown.
80 int shutdown_pipe_[2]; 76 int shutdown_pipe_[2];
81 77
82 // Flag set to true when startup was successful. 78 // Flag set to true when startup was successful.
83 bool valid_; 79 bool valid_;
84 80
85 DISALLOW_COPY_AND_ASSIGN(InotifyReader); 81 DISALLOW_COPY_AND_ASSIGN(InotifyReader);
86 }; 82 };
87 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:
147 RegisterSubtreeWatchesTask(DirectoryWatcherImpl* watcher,
148 const FilePath& path)
149 : watcher_(watcher),
150 path_(path) {
151 }
152
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:
175 DirectoryWatcherImplNotifyTask(DirectoryWatcher::Delegate* delegate,
176 const FilePath& path)
177 : delegate_(delegate),
178 path_(path) {
179 }
180
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
88 class InotifyReaderTask : public Task { 192 class InotifyReaderTask : public Task {
89 public: 193 public:
90 InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd) 194 InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
91 : reader_(reader), 195 : reader_(reader),
92 inotify_fd_(inotify_fd), 196 inotify_fd_(inotify_fd),
93 shutdown_fd_(shutdown_fd) { 197 shutdown_fd_(shutdown_fd) {
94 } 198 }
95 199
96 virtual void Run() { 200 virtual void Run() {
97 while (true) { 201 while (true) {
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 } 248 }
145 249
146 private: 250 private:
147 InotifyReader* reader_; 251 InotifyReader* reader_;
148 int inotify_fd_; 252 int inotify_fd_;
149 int shutdown_fd_; 253 int shutdown_fd_;
150 254
151 DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask); 255 DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
152 }; 256 };
153 257
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() 258 InotifyReader::InotifyReader()
174 : thread_("inotify_reader"), 259 : thread_("inotify_reader"),
175 inotify_fd_(inotify_init()), 260 inotify_fd_(inotify_init()),
176 valid_(false) { 261 valid_(false) {
177 shutdown_pipe_[0] = -1; 262 shutdown_pipe_[0] = -1;
178 shutdown_pipe_[1] = -1; 263 shutdown_pipe_[1] = -1;
179 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) { 264 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
180 thread_.message_loop()->PostTask( 265 thread_.message_loop()->PostTask(
181 FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0])); 266 FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
182 valid_ = true; 267 valid_ = true;
183 } 268 }
184 } 269 }
185 270
186 InotifyReader::~InotifyReader() { 271 InotifyReader::~InotifyReader() {
187 if (valid_) { 272 if (valid_) {
188 // Write to the self-pipe so that the select call in InotifyReaderTask 273 // Write to the self-pipe so that the select call in InotifyReaderTask
189 // returns. 274 // returns.
190 HANDLE_EINTR(write(shutdown_pipe_[1], "", 1)); 275 HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
191 thread_.Stop(); 276 thread_.Stop();
192 } 277 }
193 if (inotify_fd_ >= 0) 278 if (inotify_fd_ >= 0)
194 close(inotify_fd_); 279 close(inotify_fd_);
195 if (shutdown_pipe_[0] >= 0) 280 if (shutdown_pipe_[0] >= 0)
196 close(shutdown_pipe_[0]); 281 close(shutdown_pipe_[0]);
197 if (shutdown_pipe_[1] >= 0) 282 if (shutdown_pipe_[1] >= 0)
198 close(shutdown_pipe_[1]); 283 close(shutdown_pipe_[1]);
199 } 284 }
200 285
201 InotifyReader::Watch InotifyReader::AddWatch( 286 InotifyReader::Watch InotifyReader::AddWatch(
202 const FilePath& path, DirectoryWatcher::Delegate* delegate) { 287 const FilePath& path, DirectoryWatcherImpl* watcher) {
288
203 if (!valid_) 289 if (!valid_)
204 return kInvalidWatch; 290 return kInvalidWatch;
205 291
206 AutoLock auto_lock(lock_); 292 AutoLock auto_lock(lock_);
207 293
208 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), 294 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
209 IN_CREATE | IN_DELETE | 295 IN_CREATE | IN_DELETE |
210 IN_CLOSE_WRITE | IN_MOVE); 296 IN_CLOSE_WRITE | IN_MOVE);
297
211 if (watch == kInvalidWatch) 298 if (watch == kInvalidWatch)
212 return kInvalidWatch; 299 return kInvalidWatch;
213 300
214 if (paths_[watch].empty()) 301 if (paths_[watch].empty())
215 paths_[watch] = path; // We don't yet watch this path. 302 paths_[watch] = path; // We don't yet watch this path.
216 303
217 delegates_[watch].insert(std::make_pair(delegate, MessageLoop::current())); 304 watchers_[watch].insert(watcher);
218 305
219 return watch; 306 return watch;
220 } 307 }
221 308
222 bool InotifyReader::RemoveWatch(Watch watch, 309 bool InotifyReader::RemoveWatch(Watch watch,
223 DirectoryWatcher::Delegate* delegate) { 310 DirectoryWatcherImpl* watcher) {
224 if (!valid_) 311 if (!valid_)
225 return false; 312 return false;
226 313
227 AutoLock auto_lock(lock_); 314 AutoLock auto_lock(lock_);
228 315
229 if (paths_[watch].empty()) 316 if (paths_[watch].empty())
230 return false; // We don't recognize this watch. 317 return false; // We don't recognize this watch.
231 318
232 // Only erase one occurrence of delegate (there may be more). 319 watchers_[watch].erase(watcher);
233 delegates_[watch].erase(
234 delegates_[watch].find(std::make_pair(delegate, MessageLoop::current())));
235 320
236 if (delegates_[watch].empty()) { 321 if (watchers_[watch].empty()) {
237 paths_.erase(watch); 322 paths_.erase(watch);
238 delegates_.erase(watch); 323 watchers_.erase(watch);
239
240 return (inotify_rm_watch(inotify_fd_, watch) == 0); 324 return (inotify_rm_watch(inotify_fd_, watch) == 0);
241 } 325 }
242 326
243 return true; 327 return true;
244 } 328 }
245 329
246 void InotifyReader::OnInotifyEvent(inotify_event* event) { 330 void InotifyReader::OnInotifyEvent(const inotify_event* event) {
247 if (event->mask & IN_IGNORED) 331 if (event->mask & IN_IGNORED)
248 return; 332 return;
249 333
250 DelegateSet delegates_to_notify; 334 WatcherSet watchers_to_notify;
251 FilePath changed_path; 335 FilePath changed_path;
252 336
253 { 337 {
254 AutoLock auto_lock(lock_); 338 AutoLock auto_lock(lock_);
255 changed_path = paths_[event->wd]; 339 changed_path = paths_[event->wd];
256 delegates_to_notify.insert(delegates_[event->wd].begin(), 340 watchers_to_notify.insert(watchers_[event->wd].begin(),
257 delegates_[event->wd].end()); 341 watchers_[event->wd].end());
258 } 342 }
259 343
260 DelegateSet::iterator i; 344 for (WatcherSet::iterator watcher = watchers_to_notify.begin();
261 for (i = delegates_to_notify.begin(); i != delegates_to_notify.end(); ++i) { 345 watcher != watchers_to_notify.end();
262 DirectoryWatcher::Delegate* delegate = i->first; 346 ++watcher) {
263 MessageLoop* loop = i->second; 347 (*watcher)->OnInotifyEvent(event);
264 loop->PostTask(FROM_HERE,
265 new InotifyReaderNotifyTask(delegate, changed_path));
266 } 348 }
267 } 349 }
268 350
269 class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate { 351 DirectoryWatcherImpl::DirectoryWatcherImpl()
270 public: 352 : watch_(InotifyReader::kInvalidWatch),
271 DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {} 353 recursive_setup_finished_(false, false) {
272 ~DirectoryWatcherImpl(); 354 }
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 355
290 DirectoryWatcherImpl::~DirectoryWatcherImpl() { 356 DirectoryWatcherImpl::~DirectoryWatcherImpl() {
291 if (watch_ != InotifyReader::kInvalidWatch) 357 if (watch_ == InotifyReader::kInvalidWatch)
292 Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_); 358 return;
359
360 if (recursive_)
361 recursive_setup_finished_.Wait();
362 for (WatchSet::iterator watch = watches_.begin();
363 watch != watches_.end();
364 ++watch) {
365 Singleton<InotifyReader>::get()->RemoveWatch(*watch, this);
366 }
367 watches_.clear();
368 inodes_watched_.clear();
369 }
370
371 void DirectoryWatcherImpl::OnInotifyEvent(const inotify_event* event) {
372 loop_->PostTask(FROM_HERE,
373 new DirectoryWatcherImplNotifyTask(delegate_, root_path_));
374
375 if (!(event->mask & IN_ISDIR))
376 return;
377
378 if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
379 // TODO(phajdan.jr): add watch for this new directory.
380 NOTIMPLEMENTED();
381 } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
382 // TODO(phajdan.jr): remove our watch for this directory.
383 NOTIMPLEMENTED();
384 }
385 }
386
387 bool DirectoryWatcherImpl::IsInodeWatched(ino_t inode) const {
388 return inodes_watched_.find(inode) != inodes_watched_.end();
389 }
390
391 bool DirectoryWatcherImpl::OnEnumeratedSubtree(const FilePathSet& subtree) {
392 DCHECK(recursive_);
393
394 if (watch_ == InotifyReader::kInvalidWatch) {
395 recursive_setup_finished_.Signal();
396 return false;
397 }
398
399 bool success = true;
400 AutoLock auto_lock(lock_);
401 for (FilePathSet::iterator subdirectory = subtree.begin();
402 subdirectory != subtree.end();
403 ++subdirectory) {
404 ino_t inode;
405 if (!file_util::GetInode(*subdirectory, &inode)) {
406 success = false;
407 continue;
408 }
409 if (IsInodeWatched(inode))
410 continue;
411 InotifyReader::Watch watch =
412 Singleton<InotifyReader>::get()->AddWatch(*subdirectory, this);
413 if (watch != InotifyReader::kInvalidWatch) {
414 watches_.insert(watch);
415 inodes_watched_.insert(inode);
416 }
417 }
418 recursive_setup_finished_.Signal();
419 return success;
293 } 420 }
294 421
295 bool DirectoryWatcherImpl::Watch(const FilePath& path, 422 bool DirectoryWatcherImpl::Watch(const FilePath& path,
296 DirectoryWatcher::Delegate* delegate, bool recursive) { 423 DirectoryWatcher::Delegate* delegate,
297 DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path. 424 MessageLoop* backend_loop, bool recursive) {
298 425
299 if (recursive) { 426 // Can only watch one path.
300 // TODO(phajdan.jr): Support recursive watches. 427 DCHECK(watch_ == InotifyReader::kInvalidWatch);
301 // Unfortunately inotify has no "native" support for them, but it can be 428
302 // emulated by walking the directory tree and setting up watches for each 429 ino_t inode;
303 // directory. Of course this is ineffective for large directory trees. 430 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; 431 return false;
432
433 InotifyReader::Watch watch =
434 Singleton<InotifyReader>::get()->AddWatch(path, this);
435 if (watch == InotifyReader::kInvalidWatch)
436 return false;
437
438 delegate_ = delegate;
439 recursive_ = recursive;
440 root_path_ = path;
441 watch_ = watch;
442 loop_ = MessageLoop::current();
443
444 {
445 AutoLock auto_lock(lock_);
446 inodes_watched_.insert(inode);
447 watches_.insert(watch_);
308 } 448 }
309 449
310 delegate_ = delegate; 450 if (recursive_) {
311 path_ = path; 451 Task* subtree_task = new RegisterSubtreeWatchesTask(this, root_path_);
312 watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_); 452 if (backend_loop) {
453 backend_loop->PostTask(FROM_HERE, subtree_task);
454 } else {
455 subtree_task->Run();
456 delete subtree_task;
457 }
458 }
313 459
314 return watch_ != InotifyReader::kInvalidWatch; 460 return true;
315 } 461 }
316 462
317 } // namespace 463 } // namespace
318 464
319 DirectoryWatcher::DirectoryWatcher() { 465 DirectoryWatcher::DirectoryWatcher() {
320 impl_ = new DirectoryWatcherImpl(); 466 impl_ = new DirectoryWatcherImpl();
321 } 467 }
468
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698