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