OLD | NEW |
(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 } |
OLD | NEW |