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

Side by Side Diff: base/file_watcher_inotify.cc

Issue 661359: Add a FileWatcher to base/. (Closed)
Patch Set: 2s Created 10 years, 9 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
« no previous file with comments | « base/file_watcher.h ('k') | base/file_watcher_mac.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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/file_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 FileWatcherImpl;
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, FileWatcherImpl* watcher);
45
46 // Remove |watch|. Returns true on success.
47 bool RemoveWatch(Watch watch, FileWatcherImpl* 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<FileWatcherImpl*> 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 // Lock to protect watchers_.
64 Lock lock_;
65
66 // Separate thread on which we run blocking read for inotify events.
67 base::Thread thread_;
68
69 // File descriptor returned by inotify_init.
70 const int inotify_fd_;
71
72 // Use self-pipe trick to unblock select during shutdown.
73 int shutdown_pipe_[2];
74
75 // Flag set to true when startup was successful.
76 bool valid_;
77
78 DISALLOW_COPY_AND_ASSIGN(InotifyReader);
79 };
80
81 class FileWatcherImpl : public FileWatcher::PlatformDelegate {
82 public:
83 FileWatcherImpl();
84 ~FileWatcherImpl();
85
86 // Called for each event coming from the watch.
87 void OnInotifyEvent(const inotify_event* event);
88
89 // Start watching |path| for changes and notify |delegate| on each change.
90 // Returns true if watch for |path| has been added successfully.
91 virtual bool Watch(const FilePath& path, FileWatcher::Delegate* delegate,
92 MessageLoop* backend_loop);
93
94 private:
95 // Delegate to notify upon changes.
96 FileWatcher::Delegate* delegate_;
97
98 // Watch returned by InotifyReader.
99 InotifyReader::Watch watch_;
100
101 // The file we're watching.
102 FilePath path_;
103
104 // Loop where we post file change notifications to.
105 MessageLoop* loop_;
106
107 DISALLOW_COPY_AND_ASSIGN(FileWatcherImpl);
108 };
109
110 class FileWatcherImplNotifyTask : public Task {
111 public:
112 FileWatcherImplNotifyTask(FileWatcher::Delegate* delegate,
113 const FilePath& path)
114 : delegate_(delegate), path_(path) {
115 }
116
117 virtual void Run() {
118 delegate_->OnFileChanged(path_);
119 }
120
121 private:
122 FileWatcher::Delegate* delegate_;
123 FilePath path_;
124
125 DISALLOW_COPY_AND_ASSIGN(FileWatcherImplNotifyTask);
126 };
127
128 class InotifyReaderTask : public Task {
129 public:
130 InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
131 : reader_(reader),
132 inotify_fd_(inotify_fd),
133 shutdown_fd_(shutdown_fd) {
134 }
135
136 virtual void Run() {
137 while (true) {
138 fd_set rfds;
139 FD_ZERO(&rfds);
140 FD_SET(inotify_fd_, &rfds);
141 FD_SET(shutdown_fd_, &rfds);
142
143 // Wait until some inotify events are available.
144 int select_result =
145 HANDLE_EINTR(select(std::max(inotify_fd_, shutdown_fd_) + 1,
146 &rfds, NULL, NULL, NULL));
147 if (select_result < 0) {
148 DPLOG(WARNING) << "select failed";
149 return;
150 }
151
152 if (FD_ISSET(shutdown_fd_, &rfds))
153 return;
154
155 // Adjust buffer size to current event queue size.
156 int buffer_size;
157 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD,
158 &buffer_size));
159
160 if (ioctl_result != 0) {
161 DPLOG(WARNING) << "ioctl failed";
162 return;
163 }
164
165 std::vector<char> buffer(buffer_size);
166
167 ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd_, &buffer[0],
168 buffer_size));
169
170 if (bytes_read < 0) {
171 DPLOG(WARNING) << "read from inotify fd failed";
172 return;
173 }
174
175 ssize_t i = 0;
176 while (i < bytes_read) {
177 inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
178 size_t event_size = sizeof(inotify_event) + event->len;
179 DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
180 reader_->OnInotifyEvent(event);
181 i += event_size;
182 }
183 }
184 }
185
186 private:
187 InotifyReader* reader_;
188 int inotify_fd_;
189 int shutdown_fd_;
190
191 DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
192 };
193
194 InotifyReader::InotifyReader()
195 : thread_("inotify_reader"),
196 inotify_fd_(inotify_init()),
197 valid_(false) {
198 shutdown_pipe_[0] = -1;
199 shutdown_pipe_[1] = -1;
200 if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
201 thread_.message_loop()->PostTask(
202 FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
203 valid_ = true;
204 }
205 }
206
207 InotifyReader::~InotifyReader() {
208 if (valid_) {
209 // Write to the self-pipe so that the select call in InotifyReaderTask
210 // returns.
211 ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
212 DPCHECK(ret > 0);
213 DCHECK_EQ(ret, 1);
214 thread_.Stop();
215 }
216 if (inotify_fd_ >= 0)
217 close(inotify_fd_);
218 if (shutdown_pipe_[0] >= 0)
219 close(shutdown_pipe_[0]);
220 if (shutdown_pipe_[1] >= 0)
221 close(shutdown_pipe_[1]);
222 }
223
224 InotifyReader::Watch InotifyReader::AddWatch(
225 const FilePath& path, FileWatcherImpl* watcher) {
226 if (!valid_)
227 return kInvalidWatch;
228
229 AutoLock auto_lock(lock_);
230
231 Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
232 IN_CREATE | IN_DELETE |
233 IN_CLOSE_WRITE | IN_MOVE);
234
235 if (watch == kInvalidWatch)
236 return kInvalidWatch;
237
238 watchers_[watch].insert(watcher);
239
240 return watch;
241 }
242
243 bool InotifyReader::RemoveWatch(Watch watch,
244 FileWatcherImpl* watcher) {
245 if (!valid_)
246 return false;
247
248 AutoLock auto_lock(lock_);
249
250 watchers_[watch].erase(watcher);
251
252 if (watchers_[watch].empty()) {
253 watchers_.erase(watch);
254 return (inotify_rm_watch(inotify_fd_, watch) == 0);
255 }
256
257 return true;
258 }
259
260 void InotifyReader::OnInotifyEvent(const inotify_event* event) {
261 if (event->mask & IN_IGNORED)
262 return;
263
264 // In case you want to limit the scope of this lock, it's not sufficient
265 // to just copy things under the lock, and then run the notifications
266 // without holding the lock. FileWatcherImpl's dtor removes its watches,
267 // and to do that obtains the lock. After it finishes removing watches,
268 // it's destroyed. So, if you copy under the lock and notify without the lock,
269 // it's possible you'll copy the FileWatcherImpl which is being
270 // destroyed, then it will destroy itself, and then you'll try to notify it.
271 AutoLock auto_lock(lock_);
272
273 for (WatcherSet::iterator watcher = watchers_[event->wd].begin();
274 watcher != watchers_[event->wd].end();
275 ++watcher) {
276 (*watcher)->OnInotifyEvent(event);
277 }
278 }
279
280 FileWatcherImpl::FileWatcherImpl()
281 : watch_(InotifyReader::kInvalidWatch) {
282 }
283
284 FileWatcherImpl::~FileWatcherImpl() {
285 if (watch_ == InotifyReader::kInvalidWatch)
286 return;
287
288 Singleton<InotifyReader>::get()->RemoveWatch(watch_, this);
289 }
290
291 void FileWatcherImpl::OnInotifyEvent(const inotify_event* event) {
292 // Since we're watching the directory, filter out inotify events
293 // if it's not related to the file we're watching.
294 if (path_ != path_.DirName().Append(event->name))
295 return;
296
297 loop_->PostTask(FROM_HERE,
298 new FileWatcherImplNotifyTask(delegate_, path_));
299 }
300
301 bool FileWatcherImpl::Watch(const FilePath& path,
302 FileWatcher::Delegate* delegate,
303 MessageLoop* backend_loop) {
304 // Each FileWatcherImpl can only watch one file.
305 DCHECK(watch_ == InotifyReader::kInvalidWatch);
306
307 // It's not possible to watch a file that doesn't exist, so instead,
308 // watch the parent directory.
309 if (!file_util::PathExists(path.DirName()))
310 return false;
311
312 delegate_ = delegate;
313 path_ = path;
314 loop_ = MessageLoop::current();
315 watch_ = Singleton<InotifyReader>::get()->AddWatch(path.DirName(), this);
316 return watch_ != InotifyReader::kInvalidWatch;
317 }
318
319 } // namespace
320
321 FileWatcher::FileWatcher() {
322 impl_ = new FileWatcherImpl();
323 }
OLDNEW
« no previous file with comments | « base/file_watcher.h ('k') | base/file_watcher_mac.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698