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

Side by Side Diff: content/common/file_path_watcher/file_path_watcher_inotify.cc

Issue 6793020: Move FilePathWatcher to base/files. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: use ::operator<< Created 9 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
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 "content/common/file_path_watcher/file_path_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/lazy_instance.h"
24 #include "base/logging.h"
25 #include "base/memory/scoped_ptr.h"
26 #include "base/message_loop.h"
27 #include "base/message_loop_proxy.h"
28 #include "base/synchronization/lock.h"
29 #include "base/task.h"
30 #include "base/threading/thread.h"
31
32 namespace {
33
34 class FilePathWatcherImpl;
35
36 // Singleton to manage all inotify watches.
37 // TODO(tony): It would be nice if this wasn't a singleton.
38 // http://crbug.com/38174
39 class InotifyReader {
40 public:
41 typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
42 static const Watch kInvalidWatch = -1;
43
44 // Watch directory |path| for changes. |watcher| will be notified on each
45 // change. Returns kInvalidWatch on failure.
46 Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher);
47
48 // Remove |watch|. Returns true on success.
49 bool RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
50
51 // Callback for InotifyReaderTask.
52 void OnInotifyEvent(const inotify_event* event);
53
54 private:
55 friend struct ::base::DefaultLazyInstanceTraits<InotifyReader>;
56
57 typedef std::set<FilePathWatcherImpl*> WatcherSet;
58
59 InotifyReader();
60 ~InotifyReader();
61
62 // We keep track of which delegates want to be notified on which watches.
63 base::hash_map<Watch, WatcherSet> watchers_;
64
65 // Lock to protect watchers_.
66 base::Lock lock_;
67
68 // Separate thread on which we run blocking read for inotify events.
69 base::Thread thread_;
70
71 // File descriptor returned by inotify_init.
72 const int inotify_fd_;
73
74 // Use self-pipe trick to unblock select during shutdown.
75 int shutdown_pipe_[2];
76
77 // Flag set to true when startup was successful.
78 bool valid_;
79
80 DISALLOW_COPY_AND_ASSIGN(InotifyReader);
81 };
82
83 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
84 public MessageLoop::DestructionObserver {
85 public:
86 FilePathWatcherImpl();
87
88 // Called for each event coming from the watch. |fired_watch| identifies the
89 // watch that fired, |child| indicates what has changed, and is relative to
90 // the currently watched path for |fired_watch|. The flag |created| is true if
91 // the object appears, and |is_directory| is set when the event refers to a
92 // directory.
93 void OnFilePathChanged(InotifyReader::Watch fired_watch,
94 const FilePath::StringType& child,
95 bool created,
96 bool is_directory);
97
98 // Start watching |path| for changes and notify |delegate| on each change.
99 // Returns true if watch for |path| has been added successfully.
100 virtual bool Watch(const FilePath& path,
101 FilePathWatcher::Delegate* delegate) OVERRIDE;
102
103 // Cancel the watch. This unregisters the instance with InotifyReader.
104 virtual void Cancel() OVERRIDE;
105
106 // Deletion of the FilePathWatcher will call Cancel() to dispose of this
107 // object in the right thread. This also observes destruction of the required
108 // cleanup thread, in case it quits before Cancel() is called.
109 virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
110
111 private:
112 virtual ~FilePathWatcherImpl() {}
113
114 // Cleans up and stops observing the |message_loop_| thread.
115 void CancelOnMessageLoopThread() OVERRIDE;
116
117 // Inotify watches are installed for all directory components of |target_|. A
118 // WatchEntry instance holds the watch descriptor for a component and the
119 // subdirectory for that identifies the next component.
120 struct WatchEntry {
121 WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir)
122 : watch_(watch),
123 subdir_(subdir) {}
124
125 InotifyReader::Watch watch_;
126 FilePath::StringType subdir_;
127 };
128 typedef std::vector<WatchEntry> WatchVector;
129
130 // Reconfigure to watch for the most specific parent directory of |target_|
131 // that exists. Updates |watched_path_|. Returns true on success.
132 bool UpdateWatches() WARN_UNUSED_RESULT;
133
134 // Delegate to notify upon changes.
135 scoped_refptr<FilePathWatcher::Delegate> delegate_;
136
137 // The file or directory we're supposed to watch.
138 FilePath target_;
139
140 // The vector of watches and next component names for all path components,
141 // starting at the root directory. The last entry corresponds to the watch for
142 // |target_| and always stores an empty next component name in |subdir_|.
143 WatchVector watches_;
144
145 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
146 };
147
148 class InotifyReaderTask : public Task {
149 public:
150 InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
151 : reader_(reader),
152 inotify_fd_(inotify_fd),
153 shutdown_fd_(shutdown_fd) {
154 }
155
156 virtual void Run() {
157 while (true) {
158 fd_set rfds;
159 FD_ZERO(&rfds);
160 FD_SET(inotify_fd_, &rfds);
161 FD_SET(shutdown_fd_, &rfds);
162
163 // Wait until some inotify events are available.
164 int select_result =
165 HANDLE_EINTR(select(std::max(inotify_fd_, shutdown_fd_) + 1,
166 &rfds, NULL, NULL, NULL));
167 if (select_result < 0) {
168 DPLOG(WARNING) << "select failed";
169 return;
170 }
171
172 if (FD_ISSET(shutdown_fd_, &rfds))
173 return;
174
175 // Adjust buffer size to current event queue size.
176 int buffer_size;
177 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD,
178 &buffer_size));
179
180 if (ioctl_result != 0) {
181 DPLOG(WARNING) << "ioctl failed";
182 return;
183 }
184
185 std::vector<char> buffer(buffer_size);
186
187 ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd_, &buffer[0],
188 buffer_size));
189
190 if (bytes_read < 0) {
191 DPLOG(WARNING) << "read from inotify fd failed";
192 return;
193 }
194
195 ssize_t i = 0;
196 while (i < bytes_read) {
197 inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
198 size_t event_size = sizeof(inotify_event) + event->len;
199 DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
200 reader_->OnInotifyEvent(event);
201 i += event_size;
202 }
203 }
204 }
205
206 private:
207 InotifyReader* reader_;
208 int inotify_fd_;
209 int shutdown_fd_;
210
211 DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
212 };
213
214 static base::LazyInstance<InotifyReader> g_inotify_reader(
215 base::LINKER_INITIALIZED);
216
217 InotifyReader::InotifyReader()
218 : thread_("inotify_reader"),
219 inotify_fd_(inotify_init()),
220 valid_(false) {
221 shutdown_pipe_[0] = -1;
222 shutdown_pipe_[1] = -1;
223 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
224 thread_.message_loop()->PostTask(
225 FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
226 valid_ = true;
227 }
228 }
229
230 InotifyReader::~InotifyReader() {
231 if (valid_) {
232 // Write to the self-pipe so that the select call in InotifyReaderTask
233 // returns.
234 ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
235 DPCHECK(ret > 0);
236 DCHECK_EQ(ret, 1);
237 thread_.Stop();
238 }
239 if (inotify_fd_ >= 0)
240 close(inotify_fd_);
241 if (shutdown_pipe_[0] >= 0)
242 close(shutdown_pipe_[0]);
243 if (shutdown_pipe_[1] >= 0)
244 close(shutdown_pipe_[1]);
245 }
246
247 InotifyReader::Watch InotifyReader::AddWatch(
248 const FilePath& path, FilePathWatcherImpl* watcher) {
249 if (!valid_)
250 return kInvalidWatch;
251
252 base::AutoLock auto_lock(lock_);
253
254 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
255 IN_CREATE | IN_DELETE |
256 IN_CLOSE_WRITE | IN_MOVE |
257 IN_ONLYDIR);
258
259 if (watch == kInvalidWatch)
260 return kInvalidWatch;
261
262 watchers_[watch].insert(watcher);
263
264 return watch;
265 }
266
267 bool InotifyReader::RemoveWatch(Watch watch,
268 FilePathWatcherImpl* watcher) {
269 if (!valid_)
270 return false;
271
272 base::AutoLock auto_lock(lock_);
273
274 watchers_[watch].erase(watcher);
275
276 if (watchers_[watch].empty()) {
277 watchers_.erase(watch);
278 return (inotify_rm_watch(inotify_fd_, watch) == 0);
279 }
280
281 return true;
282 }
283
284 void InotifyReader::OnInotifyEvent(const inotify_event* event) {
285 if (event->mask & IN_IGNORED)
286 return;
287
288 FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL(""));
289 base::AutoLock auto_lock(lock_);
290
291 for (WatcherSet::iterator watcher = watchers_[event->wd].begin();
292 watcher != watchers_[event->wd].end();
293 ++watcher) {
294 (*watcher)->OnFilePathChanged(event->wd,
295 child,
296 event->mask & (IN_CREATE | IN_MOVED_TO),
297 event->mask & IN_ISDIR);
298 }
299 }
300
301 FilePathWatcherImpl::FilePathWatcherImpl()
302 : delegate_(NULL) {
303 }
304
305 void FilePathWatcherImpl::OnFilePathChanged(
306 InotifyReader::Watch fired_watch,
307 const FilePath::StringType& child,
308 bool created,
309 bool is_directory) {
310
311 if (!message_loop()->BelongsToCurrentThread()) {
312 // Switch to message_loop_ to access watches_ safely.
313 message_loop()->PostTask(FROM_HERE,
314 NewRunnableMethod(this,
315 &FilePathWatcherImpl::OnFilePathChanged,
316 fired_watch,
317 child,
318 created,
319 is_directory));
320 return;
321 }
322
323 DCHECK(MessageLoopForIO::current());
324
325 // Find the entry in |watches_| that corresponds to |fired_watch|.
326 WatchVector::const_iterator watch_entry(watches_.begin());
327 for ( ; watch_entry != watches_.end(); ++watch_entry) {
328 if (fired_watch == watch_entry->watch_)
329 break;
330 }
331
332 // If this notification is from a previous generation of watches or the watch
333 // has been cancelled (|watches_| is empty then), bail out.
334 if (watch_entry == watches_.end())
335 return;
336
337 // Check whether a path component of |target_| changed.
338 bool change_on_target_path = child.empty() || child == watch_entry->subdir_;
339
340 // Check whether the change references |target_| or a direct child.
341 DCHECK(watch_entry->subdir_.empty() || (watch_entry + 1) != watches_.end());
342 bool target_changed = watch_entry->subdir_.empty() ||
343 (watch_entry->subdir_ == child && (++watch_entry)->subdir_.empty());
344
345 // Update watches if a directory component of the |target_| path (dis)appears.
346 if (is_directory && change_on_target_path && !UpdateWatches()) {
347 delegate_->OnFilePathError(target_);
348 return;
349 }
350
351 // Report the following events:
352 // - The target or a direct child of the target got changed (in case the
353 // watched path refers to a directory).
354 // - One of the parent directories got moved or deleted, since the target
355 // disappears in this case.
356 // - One of the parent directories appears. The event corresponding to the
357 // target appearing might have been missed in this case, so recheck.
358 if (target_changed ||
359 (change_on_target_path && !created) ||
360 (change_on_target_path && file_util::PathExists(target_))) {
361 delegate_->OnFilePathChanged(target_);
362 }
363 }
364
365 bool FilePathWatcherImpl::Watch(const FilePath& path,
366 FilePathWatcher::Delegate* delegate) {
367 DCHECK(target_.empty());
368 DCHECK(MessageLoopForIO::current());
369
370 set_message_loop(base::MessageLoopProxy::CreateForCurrentThread());
371 delegate_ = delegate;
372 target_ = path;
373 MessageLoop::current()->AddDestructionObserver(this);
374
375 std::vector<FilePath::StringType> comps;
376 target_.GetComponents(&comps);
377 DCHECK(!comps.empty());
378 for (std::vector<FilePath::StringType>::const_iterator comp(++comps.begin());
379 comp != comps.end(); ++comp) {
380 watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, *comp));
381 }
382 watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch,
383 FilePath::StringType()));
384 return UpdateWatches();
385 }
386
387 void FilePathWatcherImpl::Cancel() {
388 if (!delegate_) {
389 // Watch was never called, or the |message_loop_| thread is already gone.
390 set_cancelled();
391 return;
392 }
393
394 // Switch to the message_loop_ if necessary so we can access |watches_|.
395 if (!message_loop()->BelongsToCurrentThread()) {
396 message_loop()->PostTask(FROM_HERE,
397 new FilePathWatcher::CancelTask(this));
398 } else {
399 CancelOnMessageLoopThread();
400 }
401 }
402
403 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
404 if (!is_cancelled()) {
405 set_cancelled();
406 MessageLoop::current()->RemoveDestructionObserver(this);
407
408 for (WatchVector::iterator watch_entry(watches_.begin());
409 watch_entry != watches_.end(); ++watch_entry) {
410 if (watch_entry->watch_ != InotifyReader::kInvalidWatch)
411 g_inotify_reader.Get().RemoveWatch(watch_entry->watch_, this);
412 }
413 watches_.clear();
414 delegate_ = NULL;
415 target_.clear();
416 }
417 }
418
419 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
420 CancelOnMessageLoopThread();
421 }
422
423 bool FilePathWatcherImpl::UpdateWatches() {
424 // Ensure this runs on the message_loop_ exclusively in order to avoid
425 // concurrency issues.
426 DCHECK(message_loop()->BelongsToCurrentThread());
427
428 // Walk the list of watches and update them as we go.
429 FilePath path(FILE_PATH_LITERAL("/"));
430 bool path_valid = true;
431 for (WatchVector::iterator watch_entry(watches_.begin());
432 watch_entry != watches_.end(); ++watch_entry) {
433 InotifyReader::Watch old_watch = watch_entry->watch_;
434 if (path_valid) {
435 watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this);
436 if (watch_entry->watch_ == InotifyReader::kInvalidWatch) {
437 path_valid = false;
438 }
439 } else {
440 watch_entry->watch_ = InotifyReader::kInvalidWatch;
441 }
442 if (old_watch != InotifyReader::kInvalidWatch &&
443 old_watch != watch_entry->watch_) {
444 g_inotify_reader.Get().RemoveWatch(old_watch, this);
445 }
446 path = path.Append(watch_entry->subdir_);
447 }
448
449 return true;
450 }
451
452 } // namespace
453
454 FilePathWatcher::FilePathWatcher() {
455 impl_ = new FilePathWatcherImpl();
456 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698