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