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