1 // Copyright (c) 2012 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/files/file_path_watcher_kqueue.h" 6 7 #include <fcntl.h> 8 #include <sys/param.h> 9 10 #include "base/bind.h" 11 #include "base/file_util.h" 12 #include "base/logging.h" 13 #include "base/strings/stringprintf.h" 14 15 // On some platforms these are not defined. 16 #if !defined(EV_RECEIPT) 17 #define EV_RECEIPT 0 18 #endif 19 #if !defined(O_EVTONLY) 20 #define O_EVTONLY O_RDONLY 21 #endif 22 23 namespace base { 24 25 FilePathWatcherKQueue::FilePathWatcherKQueue() : kqueue_(-1) {} 26 27 FilePathWatcherKQueue::~FilePathWatcherKQueue() {} 28 29 void FilePathWatcherKQueue::ReleaseEvent(struct kevent& event) { 30 CloseFileDescriptor(&event.ident); 31 EventData* entry = EventDataForKevent(event); 32 delete entry; 33 event.udata = NULL; 34 } 35 36 int FilePathWatcherKQueue::EventsForPath(FilePath path, EventVector* events) { 37 DCHECK(MessageLoopForIO::current()); 38 // Make sure that we are working with a clean slate. 39 DCHECK(events->empty()); 40 41 std::vector<FilePath::StringType> components; 42 path.GetComponents(&components); 43 44 if (components.size() < 1) { 45 return -1; 46 } 47 48 int last_existing_entry = 0; 49 FilePath built_path; 50 bool path_still_exists = true; 51 for (std::vector<FilePath::StringType>::iterator i = components.begin(); 52 i != components.end(); ++i) { 53 if (i == components.begin()) { 54 built_path = FilePath(*i); 55 } else { 56 built_path = built_path.Append(*i); 57 } 58 uintptr_t fd = kNoFileDescriptor; 59 if (path_still_exists) { 60 fd = FileDescriptorForPath(built_path); 61 if (fd == kNoFileDescriptor) { 62 path_still_exists = false; 63 } else { 64 ++last_existing_entry; 65 } 66 } 67 FilePath::StringType subdir = (i != (components.end() - 1)) ? *(i + 1) : ""; 68 EventData* data = new EventData(built_path, subdir); 69 struct kevent event; 70 EV_SET(&event, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR | EV_RECEIPT), 71 (NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB | 72 NOTE_RENAME | NOTE_REVOKE | NOTE_EXTEND), 0, data); 73 events->push_back(event); 74 } 75 return last_existing_entry; 76 } 77 78 uintptr_t FilePathWatcherKQueue::FileDescriptorForPath(const FilePath& path) { 79 int fd = HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY)); 80 if (fd == -1) 81 return kNoFileDescriptor; 82 return fd; 83 } 84 85 void FilePathWatcherKQueue::CloseFileDescriptor(uintptr_t* fd) { 86 if (*fd == kNoFileDescriptor) { 87 return; 88 } 89 90 if (IGNORE_EINTR(close(*fd)) != 0) { 91 DPLOG(ERROR) << "close"; 92 } 93 *fd = kNoFileDescriptor; 94 } 95 96 bool FilePathWatcherKQueue::AreKeventValuesValid(struct kevent* kevents, 97 int count) { 98 if (count < 0) { 99 DPLOG(ERROR) << "kevent"; 100 return false; 101 } 102 bool valid = true; 103 for (int i = 0; i < count; ++i) { 104 if (kevents[i].flags & EV_ERROR && kevents[i].data) { 105 // Find the kevent in |events_| that matches the kevent with the error. 106 EventVector::iterator event = events_.begin(); 107 for (; event != events_.end(); ++event) { 108 if (event->ident == kevents[i].ident) { 109 break; 110 } 111 } 112 std::string path_name; 113 if (event != events_.end()) { 114 EventData* event_data = EventDataForKevent(*event); 115 if (event_data != NULL) { 116 path_name = event_data->path_.value(); 117 } 118 } 119 if (path_name.empty()) { 120 path_name = base::StringPrintf( 121 "fd %ld", reinterpret_cast<long>(&kevents[i].ident)); 122 } 123 DLOG(ERROR) << "Error: " << kevents[i].data << " for " << path_name; 124 valid = false; 125 } 126 } 127 return valid; 128 } 129 130 void FilePathWatcherKQueue::HandleAttributesChange( 131 const EventVector::iterator& event, 132 bool* target_file_affected, 133 bool* update_watches) { 134 EventVector::iterator next_event = event + 1; 135 EventData* next_event_data = EventDataForKevent(*next_event); 136 // Check to see if the next item in path is still accessible. 137 uintptr_t have_access = FileDescriptorForPath(next_event_data->path_); 138 if (have_access == kNoFileDescriptor) { 139 *target_file_affected = true; 140 *update_watches = true; 141 EventVector::iterator local_event(event); 142 for (; local_event != events_.end(); ++local_event) { 143 // Close all nodes from the event down. This has the side effect of 144 // potentially rendering other events in |updates| invalid. 145 // There is no need to remove the events from |kqueue_| because this 146 // happens as a side effect of closing the file descriptor. 147 CloseFileDescriptor(&local_event->ident); 148 } 149 } else { 150 CloseFileDescriptor(&have_access); 151 } 152 } 153 154 void FilePathWatcherKQueue::HandleDeleteOrMoveChange( 155 const EventVector::iterator& event, 156 bool* target_file_affected, 157 bool* update_watches) { 158 *target_file_affected = true; 159 *update_watches = true; 160 EventVector::iterator local_event(event); 161 for (; local_event != events_.end(); ++local_event) { 162 // Close all nodes from the event down. This has the side effect of 163 // potentially rendering other events in |updates| invalid. 164 // There is no need to remove the events from |kqueue_| because this 165 // happens as a side effect of closing the file descriptor. 166 CloseFileDescriptor(&local_event->ident); 167 } 168 } 169 170 void FilePathWatcherKQueue::HandleCreateItemChange( 171 const EventVector::iterator& event, 172 bool* target_file_affected, 173 bool* update_watches) { 174 // Get the next item in the path. 175 EventVector::iterator next_event = event + 1; 176 // Check to see if it already has a valid file descriptor. 177 if (!IsKeventFileDescriptorOpen(*next_event)) { 178 EventData* next_event_data = EventDataForKevent(*next_event); 179 // If not, attempt to open a file descriptor for it. 180 next_event->ident = FileDescriptorForPath(next_event_data->path_); 181 if (IsKeventFileDescriptorOpen(*next_event)) { 182 *update_watches = true; 183 if (next_event_data->subdir_.empty()) { 184 *target_file_affected = true; 185 } 186 } 187 } 188 } 189 190 bool FilePathWatcherKQueue::UpdateWatches(bool* target_file_affected) { 191 // Iterate over events adding kevents for items that exist to the kqueue. 192 // Then check to see if new components in the path have been created. 193 // Repeat until no new components in the path are detected. 194 // This is to get around races in directory creation in a watched path. 195 bool update_watches = true; 196 while (update_watches) { 197 size_t valid; 198 for (valid = 0; valid < events_.size(); ++valid) { 199 if (!IsKeventFileDescriptorOpen(events_[valid])) { 200 break; 201 } 202 } 203 if (valid == 0) { 204 // The root of the file path is inaccessible? 205 return false; 206 } 207 208 EventVector updates(valid); 209 int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], valid, &updates[0], 210 valid, NULL)); 211 if (!AreKeventValuesValid(&updates[0], count)) { 212 return false; 213 } 214 update_watches = false; 215 for (; valid < events_.size(); ++valid) { 216 EventData* event_data = EventDataForKevent(events_[valid]); 217 events_[valid].ident = FileDescriptorForPath(event_data->path_); 218 if (IsKeventFileDescriptorOpen(events_[valid])) { 219 update_watches = true; 220 if (event_data->subdir_.empty()) { 221 *target_file_affected = true; 222 } 223 } else { 224 break; 225 } 226 } 227 } 228 return true; 229 } 230 231 void FilePathWatcherKQueue::OnFileCanReadWithoutBlocking(int fd) { 232 DCHECK(MessageLoopForIO::current()); 233 DCHECK_EQ(fd, kqueue_); 234 DCHECK(events_.size()); 235 236 // Request the file system update notifications that have occurred and return 237 // them in |updates|. |count| will contain the number of updates that have 238 // occurred. 239 EventVector updates(events_.size()); 240 struct timespec timeout = {0, 0}; 241 int count = HANDLE_EINTR(kevent(kqueue_, NULL, 0, &updates[0], updates.size(), 242 &timeout)); 243 244 // Error values are stored within updates, so check to make sure that no 245 // errors occurred. 246 if (!AreKeventValuesValid(&updates[0], count)) { 247 callback_.Run(target_, true /* error */); 248 Cancel(); 249 return; 250 } 251 252 bool update_watches = false; 253 bool send_notification = false; 254 255 // Iterate through each of the updates and react to them. 256 for (int i = 0; i < count; ++i) { 257 // Find our kevent record that matches the update notification. 258 EventVector::iterator event = events_.begin(); 259 for (; event != events_.end(); ++event) { 260 if (!IsKeventFileDescriptorOpen(*event) || 261 event->ident == updates[i].ident) { 262 break; 263 } 264 } 265 if (event == events_.end() || !IsKeventFileDescriptorOpen(*event)) { 266 // The event may no longer exist in |events_| because another event 267 // modified |events_| in such a way to make it invalid. For example if 268 // the path is /foo/bar/bam and foo is deleted, NOTE_DELETE events for 269 // foo, bar and bam will be sent. If foo is processed first, then 270 // the file descriptors for bar and bam will already be closed and set 271 // to -1 before they get a chance to be processed. 272 continue; 273 } 274 275 EventData* event_data = EventDataForKevent(*event); 276 277 // If the subdir is empty, this is the last item on the path and is the 278 // target file. 279 bool target_file_affected = event_data->subdir_.empty(); 280 if ((updates[i].fflags & NOTE_ATTRIB) && !target_file_affected) { 281 HandleAttributesChange(event, &target_file_affected, &update_watches); 282 } 283 if (updates[i].fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) { 284 HandleDeleteOrMoveChange(event, &target_file_affected, &update_watches); 285 } 286 if ((updates[i].fflags & NOTE_WRITE) && !target_file_affected) { 287 HandleCreateItemChange(event, &target_file_affected, &update_watches); 288 } 289 send_notification |= target_file_affected; 290 } 291 292 if (update_watches) { 293 if (!UpdateWatches(&send_notification)) { 294 callback_.Run(target_, true /* error */); 295 Cancel(); 296 } 297 } 298 299 if (send_notification) { 300 callback_.Run(target_, false); 301 } 302 } 303 304 void FilePathWatcherKQueue::OnFileCanWriteWithoutBlocking(int fd) { 305 NOTREACHED(); 306 } 307 308 void FilePathWatcherKQueue::WillDestroyCurrentMessageLoop() { 309 CancelOnMessageLoopThread(); 310 } 311 312 bool FilePathWatcherKQueue::Watch(const FilePath& path, 313 bool recursive, 314 const FilePathWatcher::Callback& callback) { 315 DCHECK(MessageLoopForIO::current()); 316 DCHECK(target_.value().empty()); // Can only watch one path. 317 DCHECK(!callback.is_null()); 318 DCHECK_EQ(kqueue_, -1); 319 320 if (recursive) { 321 // Recursive watch is not supported using kqueue. 322 NOTIMPLEMENTED(); 323 return false; 324 } 325 326 callback_ = callback; 327 target_ = path; 328 329 MessageLoop::current()->AddDestructionObserver(this); 330 io_message_loop_ = base::MessageLoopProxy::current(); 331 332 kqueue_ = kqueue(); 333 if (kqueue_ == -1) { 334 DPLOG(ERROR) << "kqueue"; 335 return false; 336 } 337 338 int last_entry = EventsForPath(target_, &events_); 339 DCHECK_NE(last_entry, 0); 340 341 EventVector responses(last_entry); 342 343 int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], last_entry, 344 &responses[0], last_entry, NULL)); 345 if (!AreKeventValuesValid(&responses[0], count)) { 346 // Calling Cancel() here to close any file descriptors that were opened. 347 // This would happen in the destructor anyways, but FilePathWatchers tend to 348 // be long lived, and if an error has occurred, there is no reason to waste 349 // the file descriptors. 350 Cancel(); 351 return false; 352 } 353 354 return MessageLoopForIO::current()->WatchFileDescriptor( 355 kqueue_, true, MessageLoopForIO::WATCH_READ, &kqueue_watcher_, this); 356 } 357 358 void FilePathWatcherKQueue::Cancel() { 359 base::MessageLoopProxy* proxy = io_message_loop_.get(); 360 if (!proxy) { 361 set_cancelled(); 362 return; 363 } 364 if (!proxy->BelongsToCurrentThread()) { 365 proxy->PostTask(FROM_HERE, 366 base::Bind(&FilePathWatcherKQueue::Cancel, this)); 367 return; 368 } 369 CancelOnMessageLoopThread(); 370 } 371 372 void FilePathWatcherKQueue::CancelOnMessageLoopThread() { 373 DCHECK(MessageLoopForIO::current()); 374 if (!is_cancelled()) { 375 set_cancelled(); 376 kqueue_watcher_.StopWatchingFileDescriptor(); 377 if (IGNORE_EINTR(close(kqueue_)) != 0) { 378 DPLOG(ERROR) << "close kqueue"; 379 } 380 kqueue_ = -1; 381 std::for_each(events_.begin(), events_.end(), ReleaseEvent); 382 events_.clear(); 383 io_message_loop_ = NULL; 384 MessageLoop::current()->RemoveDestructionObserver(this); 385 callback_.Reset(); 386 } 387 } 388 389 } // namespace base 390