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 "base/files/file_path_watcher.h" 6 7#include "base/file_path.h" 8#include "base/file_util.h" 9#include "base/logging.h" 10#include "base/memory/ref_counted.h" 11#include "base/message_loop_proxy.h" 12#include "base/time.h" 13#include "base/win/object_watcher.h" 14 15namespace base { 16namespace files { 17 18namespace { 19 20class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, 21 public base::win::ObjectWatcher::Delegate, 22 public MessageLoop::DestructionObserver { 23 public: 24 FilePathWatcherImpl() : delegate_(NULL), handle_(INVALID_HANDLE_VALUE) {} 25 26 // FilePathWatcher::PlatformDelegate overrides. 27 virtual bool Watch(const FilePath& path, 28 FilePathWatcher::Delegate* delegate) OVERRIDE; 29 virtual void Cancel() OVERRIDE; 30 31 // Deletion of the FilePathWatcher will call Cancel() to dispose of this 32 // object in the right thread. This also observes destruction of the required 33 // cleanup thread, in case it quits before Cancel() is called. 34 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; 35 36 // Callback from MessageLoopForIO. 37 virtual void OnObjectSignaled(HANDLE object); 38 39 private: 40 virtual ~FilePathWatcherImpl() {} 41 42 // Setup a watch handle for directory |dir|. Returns true if no fatal error 43 // occurs. |handle| will receive the handle value if |dir| is watchable, 44 // otherwise INVALID_HANDLE_VALUE. 45 static bool SetupWatchHandle(const FilePath& dir, HANDLE* handle) 46 WARN_UNUSED_RESULT; 47 48 // (Re-)Initialize the watch handle. 49 bool UpdateWatch() WARN_UNUSED_RESULT; 50 51 // Destroy the watch handle. 52 void DestroyWatch(); 53 54 // Cleans up and stops observing the |message_loop_| thread. 55 void CancelOnMessageLoopThread() OVERRIDE; 56 57 // Delegate to notify upon changes. 58 scoped_refptr<FilePathWatcher::Delegate> delegate_; 59 60 // Path we're supposed to watch (passed to delegate). 61 FilePath target_; 62 63 // Handle for FindFirstChangeNotification. 64 HANDLE handle_; 65 66 // ObjectWatcher to watch handle_ for events. 67 base::win::ObjectWatcher watcher_; 68 69 // Keep track of the last modified time of the file. We use nulltime 70 // to represent the file not existing. 71 base::Time last_modified_; 72 73 // The time at which we processed the first notification with the 74 // |last_modified_| time stamp. 75 base::Time first_notification_; 76 77 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); 78}; 79 80bool FilePathWatcherImpl::Watch(const FilePath& path, 81 FilePathWatcher::Delegate* delegate) { 82 DCHECK(target_.value().empty()); // Can only watch one path. 83 84 set_message_loop(base::MessageLoopProxy::CreateForCurrentThread()); 85 delegate_ = delegate; 86 target_ = path; 87 MessageLoop::current()->AddDestructionObserver(this); 88 89 if (!UpdateWatch()) 90 return false; 91 92 watcher_.StartWatching(handle_, this); 93 94 return true; 95} 96 97void FilePathWatcherImpl::Cancel() { 98 if (!delegate_) { 99 // Watch was never called, or the |message_loop_| has already quit. 100 set_cancelled(); 101 return; 102 } 103 104 // Switch to the file thread if necessary so we can stop |watcher_|. 105 if (!message_loop()->BelongsToCurrentThread()) { 106 message_loop()->PostTask(FROM_HERE, 107 new FilePathWatcher::CancelTask(this)); 108 } else { 109 CancelOnMessageLoopThread(); 110 } 111} 112 113void FilePathWatcherImpl::CancelOnMessageLoopThread() { 114 set_cancelled(); 115 116 if (handle_ != INVALID_HANDLE_VALUE) 117 DestroyWatch(); 118 119 if (delegate_) { 120 MessageLoop::current()->RemoveDestructionObserver(this); 121 delegate_ = NULL; 122 } 123} 124 125void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { 126 CancelOnMessageLoopThread(); 127} 128 129void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) { 130 DCHECK(object == handle_); 131 // Make sure we stay alive through the body of this function. 132 scoped_refptr<FilePathWatcherImpl> keep_alive(this); 133 134 if (!UpdateWatch()) { 135 delegate_->OnFilePathError(target_); 136 return; 137 } 138 139 // Check whether the event applies to |target_| and notify the delegate. 140 base::PlatformFileInfo file_info; 141 bool file_exists = file_util::GetFileInfo(target_, &file_info); 142 if (file_exists && (last_modified_.is_null() || 143 last_modified_ != file_info.last_modified)) { 144 last_modified_ = file_info.last_modified; 145 first_notification_ = base::Time::Now(); 146 delegate_->OnFilePathChanged(target_); 147 } else if (file_exists && !first_notification_.is_null()) { 148 // The target's last modification time is equal to what's on record. This 149 // means that either an unrelated event occurred, or the target changed 150 // again (file modification times only have a resolution of 1s). Comparing 151 // file modification times against the wall clock is not reliable to find 152 // out whether the change is recent, since this code might just run too 153 // late. Moreover, there's no guarantee that file modification time and wall 154 // clock times come from the same source. 155 // 156 // Instead, the time at which the first notification carrying the current 157 // |last_notified_| time stamp is recorded. Later notifications that find 158 // the same file modification time only need to be forwarded until wall 159 // clock has advanced one second from the initial notification. After that 160 // interval, client code is guaranteed to having seen the current revision 161 // of the file. 162 if (base::Time::Now() - first_notification_ > 163 base::TimeDelta::FromSeconds(1)) { 164 // Stop further notifications for this |last_modification_| time stamp. 165 first_notification_ = base::Time(); 166 } 167 delegate_->OnFilePathChanged(target_); 168 } else if (!file_exists && !last_modified_.is_null()) { 169 last_modified_ = base::Time(); 170 delegate_->OnFilePathChanged(target_); 171 } 172 173 // The watch may have been cancelled by the callback. 174 if (handle_ != INVALID_HANDLE_VALUE) 175 watcher_.StartWatching(handle_, this); 176} 177 178// static 179bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir, 180 HANDLE* handle) { 181 *handle = FindFirstChangeNotification( 182 dir.value().c_str(), 183 false, // Don't watch subtrees 184 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | 185 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | 186 FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY); 187 if (*handle != INVALID_HANDLE_VALUE) { 188 // Make sure the handle we got points to an existing directory. It seems 189 // that windows sometimes hands out watches to direectories that are 190 // about to go away, but doesn't sent notifications if that happens. 191 if (!file_util::DirectoryExists(dir)) { 192 FindCloseChangeNotification(*handle); 193 *handle = INVALID_HANDLE_VALUE; 194 } 195 return true; 196 } 197 198 // If FindFirstChangeNotification failed because the target directory 199 // doesn't exist, access is denied (happens if the file is already gone but 200 // there are still handles open), or the target is not a directory, try the 201 // immediate parent directory instead. 202 DWORD error_code = GetLastError(); 203 if (error_code != ERROR_FILE_NOT_FOUND && 204 error_code != ERROR_PATH_NOT_FOUND && 205 error_code != ERROR_ACCESS_DENIED && 206 error_code != ERROR_SHARING_VIOLATION && 207 error_code != ERROR_DIRECTORY) { 208 using ::operator<<; // Pick the right operator<< below. 209 PLOG(ERROR) << "FindFirstChangeNotification failed for " 210 << dir.value(); 211 return false; 212 } 213 214 return true; 215} 216 217bool FilePathWatcherImpl::UpdateWatch() { 218 if (handle_ != INVALID_HANDLE_VALUE) 219 DestroyWatch(); 220 221 base::PlatformFileInfo file_info; 222 if (file_util::GetFileInfo(target_, &file_info)) { 223 last_modified_ = file_info.last_modified; 224 first_notification_ = base::Time::Now(); 225 } 226 227 // Start at the target and walk up the directory chain until we succesfully 228 // create a watch handle in |handle_|. |child_dirs| keeps a stack of child 229 // directories stripped from target, in reverse order. 230 std::vector<FilePath> child_dirs; 231 FilePath watched_path(target_); 232 while (true) { 233 if (!SetupWatchHandle(watched_path, &handle_)) 234 return false; 235 236 // Break if a valid handle is returned. Try the parent directory otherwise. 237 if (handle_ != INVALID_HANDLE_VALUE) 238 break; 239 240 // Abort if we hit the root directory. 241 child_dirs.push_back(watched_path.BaseName()); 242 FilePath parent(watched_path.DirName()); 243 if (parent == watched_path) { 244 LOG(ERROR) << "Reached the root directory"; 245 return false; 246 } 247 watched_path = parent; 248 } 249 250 // At this point, handle_ is valid. However, the bottom-up search that the 251 // above code performs races against directory creation. So try to walk back 252 // down and see whether any children appeared in the mean time. 253 while (!child_dirs.empty()) { 254 watched_path = watched_path.Append(child_dirs.back()); 255 child_dirs.pop_back(); 256 HANDLE temp_handle = INVALID_HANDLE_VALUE; 257 if (!SetupWatchHandle(watched_path, &temp_handle)) 258 return false; 259 if (temp_handle == INVALID_HANDLE_VALUE) 260 break; 261 FindCloseChangeNotification(handle_); 262 handle_ = temp_handle; 263 } 264 265 return true; 266} 267 268void FilePathWatcherImpl::DestroyWatch() { 269 watcher_.StopWatching(); 270 FindCloseChangeNotification(handle_); 271 handle_ = INVALID_HANDLE_VALUE; 272} 273 274} // namespace 275 276FilePathWatcher::FilePathWatcher() { 277 impl_ = new FilePathWatcherImpl(); 278} 279 280} // namespace files 281} // namespace base 282