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
15 namespace base {
16 namespace files {
17
18 namespace {
19
20 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
21 public base::win::ObjectWatcher::Delegate,
22 public MessageLoop::DestructionObserver {
23 public:
FilePathWatcherImpl()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:
~FilePathWatcherImpl()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
Watch(const FilePath & path,FilePathWatcher::Delegate * delegate)80 bool 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
Cancel()97 void 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
CancelOnMessageLoopThread()113 void 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
WillDestroyCurrentMessageLoop()125 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
126 CancelOnMessageLoopThread();
127 }
128
OnObjectSignaled(HANDLE object)129 void 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
SetupWatchHandle(const FilePath & dir,HANDLE * handle)179 bool 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
UpdateWatch()217 bool 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
DestroyWatch()268 void FilePathWatcherImpl::DestroyWatch() {
269 watcher_.StopWatching();
270 FindCloseChangeNotification(handle_);
271 handle_ = INVALID_HANDLE_VALUE;
272 }
273
274 } // namespace
275
FilePathWatcher()276 FilePathWatcher::FilePathWatcher() {
277 impl_ = new FilePathWatcherImpl();
278 }
279
280 } // namespace files
281 } // namespace base
282