• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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