1// Copyright (c) 2012 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#if defined(OS_WIN)
8#include <windows.h>
9#include <aclapi.h>
10#elif defined(OS_POSIX)
11#include <sys/stat.h>
12#endif
13
14#include <set>
15
16#include "base/basictypes.h"
17#include "base/bind.h"
18#include "base/bind_helpers.h"
19#include "base/compiler_specific.h"
20#include "base/file_util.h"
21#include "base/files/file_path.h"
22#include "base/files/scoped_temp_dir.h"
23#include "base/message_loop/message_loop.h"
24#include "base/message_loop/message_loop_proxy.h"
25#include "base/run_loop.h"
26#include "base/stl_util.h"
27#include "base/strings/stringprintf.h"
28#include "base/synchronization/waitable_event.h"
29#include "base/test/test_file_util.h"
30#include "base/test/test_timeouts.h"
31#include "base/threading/thread.h"
32#include "testing/gtest/include/gtest/gtest.h"
33
34namespace base {
35
36namespace {
37
38class TestDelegate;
39
40// Aggregates notifications from the test delegates and breaks the message loop
41// the test thread is waiting on once they all came in.
42class NotificationCollector
43    : public base::RefCountedThreadSafe<NotificationCollector> {
44 public:
45  NotificationCollector()
46      : loop_(base::MessageLoopProxy::current()) {}
47
48  // Called from the file thread by the delegates.
49  void OnChange(TestDelegate* delegate) {
50    loop_->PostTask(FROM_HERE,
51                    base::Bind(&NotificationCollector::RecordChange, this,
52                               base::Unretained(delegate)));
53  }
54
55  void Register(TestDelegate* delegate) {
56    delegates_.insert(delegate);
57  }
58
59  void Reset() {
60    signaled_.clear();
61  }
62
63  bool Success() {
64    return signaled_ == delegates_;
65  }
66
67 private:
68  friend class base::RefCountedThreadSafe<NotificationCollector>;
69  ~NotificationCollector() {}
70
71  void RecordChange(TestDelegate* delegate) {
72    // Warning: |delegate| is Unretained. Do not dereference.
73    ASSERT_TRUE(loop_->BelongsToCurrentThread());
74    ASSERT_TRUE(delegates_.count(delegate));
75    signaled_.insert(delegate);
76
77    // Check whether all delegates have been signaled.
78    if (signaled_ == delegates_)
79      loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure());
80  }
81
82  // Set of registered delegates.
83  std::set<TestDelegate*> delegates_;
84
85  // Set of signaled delegates.
86  std::set<TestDelegate*> signaled_;
87
88  // The loop we should break after all delegates signaled.
89  scoped_refptr<base::MessageLoopProxy> loop_;
90};
91
92class TestDelegateBase : public SupportsWeakPtr<TestDelegateBase> {
93 public:
94  TestDelegateBase() {}
95  virtual ~TestDelegateBase() {}
96
97  virtual void OnFileChanged(const FilePath& path, bool error) = 0;
98
99 private:
100  DISALLOW_COPY_AND_ASSIGN(TestDelegateBase);
101};
102
103// A mock class for testing. Gmock is not appropriate because it is not
104// thread-safe for setting expectations. Thus the test code cannot safely
105// reset expectations while the file watcher is running.
106// Instead, TestDelegate gets the notifications from FilePathWatcher and uses
107// NotificationCollector to aggregate the results.
108class TestDelegate : public TestDelegateBase {
109 public:
110  explicit TestDelegate(NotificationCollector* collector)
111      : collector_(collector) {
112    collector_->Register(this);
113  }
114  virtual ~TestDelegate() {}
115
116  virtual void OnFileChanged(const FilePath& path, bool error) OVERRIDE {
117    if (error)
118      ADD_FAILURE() << "Error " << path.value();
119    else
120      collector_->OnChange(this);
121  }
122
123 private:
124  scoped_refptr<NotificationCollector> collector_;
125
126  DISALLOW_COPY_AND_ASSIGN(TestDelegate);
127};
128
129void SetupWatchCallback(const FilePath& target,
130                        FilePathWatcher* watcher,
131                        TestDelegateBase* delegate,
132                        bool recursive_watch,
133                        bool* result,
134                        base::WaitableEvent* completion) {
135  *result = watcher->Watch(target, recursive_watch,
136                           base::Bind(&TestDelegateBase::OnFileChanged,
137                                      delegate->AsWeakPtr()));
138  completion->Signal();
139}
140
141void QuitLoopWatchCallback(MessageLoop* loop,
142                           const FilePath& expected_path,
143                           bool expected_error,
144                           bool* flag,
145                           const FilePath& path,
146                           bool error) {
147  ASSERT_TRUE(flag);
148  *flag = true;
149  EXPECT_EQ(expected_path, path);
150  EXPECT_EQ(expected_error, error);
151  loop->PostTask(FROM_HERE, loop->QuitWhenIdleClosure());
152}
153
154class FilePathWatcherTest : public testing::Test {
155 public:
156  FilePathWatcherTest()
157      : file_thread_("FilePathWatcherTest") {}
158
159  virtual ~FilePathWatcherTest() {}
160
161 protected:
162  virtual void SetUp() OVERRIDE {
163    // Create a separate file thread in order to test proper thread usage.
164    base::Thread::Options options(MessageLoop::TYPE_IO, 0);
165    ASSERT_TRUE(file_thread_.StartWithOptions(options));
166    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
167    collector_ = new NotificationCollector();
168  }
169
170  virtual void TearDown() OVERRIDE {
171    RunLoop().RunUntilIdle();
172  }
173
174  void DeleteDelegateOnFileThread(TestDelegate* delegate) {
175    file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, delegate);
176  }
177
178  FilePath test_file() {
179    return temp_dir_.path().AppendASCII("FilePathWatcherTest");
180  }
181
182  FilePath test_link() {
183    return temp_dir_.path().AppendASCII("FilePathWatcherTest.lnk");
184  }
185
186  // Write |content| to |file|. Returns true on success.
187  bool WriteFile(const FilePath& file, const std::string& content) {
188    int write_size = file_util::WriteFile(file, content.c_str(),
189                                          content.length());
190    return write_size == static_cast<int>(content.length());
191  }
192
193  bool SetupWatch(const FilePath& target,
194                  FilePathWatcher* watcher,
195                  TestDelegateBase* delegate,
196                  bool recursive_watch) WARN_UNUSED_RESULT;
197
198  bool WaitForEvents() WARN_UNUSED_RESULT {
199    collector_->Reset();
200    loop_.Run();
201    return collector_->Success();
202  }
203
204  NotificationCollector* collector() { return collector_.get(); }
205
206  MessageLoop loop_;
207  base::Thread file_thread_;
208  ScopedTempDir temp_dir_;
209  scoped_refptr<NotificationCollector> collector_;
210
211  DISALLOW_COPY_AND_ASSIGN(FilePathWatcherTest);
212};
213
214bool FilePathWatcherTest::SetupWatch(const FilePath& target,
215                                     FilePathWatcher* watcher,
216                                     TestDelegateBase* delegate,
217                                     bool recursive_watch) {
218  base::WaitableEvent completion(false, false);
219  bool result;
220  file_thread_.message_loop_proxy()->PostTask(
221      FROM_HERE,
222      base::Bind(SetupWatchCallback,
223                 target, watcher, delegate, recursive_watch, &result,
224                 &completion));
225  completion.Wait();
226  return result;
227}
228
229// Basic test: Create the file and verify that we notice.
230TEST_F(FilePathWatcherTest, NewFile) {
231  FilePathWatcher watcher;
232  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
233  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false));
234
235  ASSERT_TRUE(WriteFile(test_file(), "content"));
236  ASSERT_TRUE(WaitForEvents());
237  DeleteDelegateOnFileThread(delegate.release());
238}
239
240// Verify that modifying the file is caught.
241TEST_F(FilePathWatcherTest, ModifiedFile) {
242  ASSERT_TRUE(WriteFile(test_file(), "content"));
243
244  FilePathWatcher watcher;
245  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
246  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false));
247
248  // Now make sure we get notified if the file is modified.
249  ASSERT_TRUE(WriteFile(test_file(), "new content"));
250  ASSERT_TRUE(WaitForEvents());
251  DeleteDelegateOnFileThread(delegate.release());
252}
253
254// Verify that moving the file into place is caught.
255TEST_F(FilePathWatcherTest, MovedFile) {
256  FilePath source_file(temp_dir_.path().AppendASCII("source"));
257  ASSERT_TRUE(WriteFile(source_file, "content"));
258
259  FilePathWatcher watcher;
260  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
261  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false));
262
263  // Now make sure we get notified if the file is modified.
264  ASSERT_TRUE(base::Move(source_file, test_file()));
265  ASSERT_TRUE(WaitForEvents());
266  DeleteDelegateOnFileThread(delegate.release());
267}
268
269TEST_F(FilePathWatcherTest, DeletedFile) {
270  ASSERT_TRUE(WriteFile(test_file(), "content"));
271
272  FilePathWatcher watcher;
273  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
274  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false));
275
276  // Now make sure we get notified if the file is deleted.
277  base::DeleteFile(test_file(), false);
278  ASSERT_TRUE(WaitForEvents());
279  DeleteDelegateOnFileThread(delegate.release());
280}
281
282// Used by the DeleteDuringNotify test below.
283// Deletes the FilePathWatcher when it's notified.
284class Deleter : public TestDelegateBase {
285 public:
286  Deleter(FilePathWatcher* watcher, MessageLoop* loop)
287      : watcher_(watcher),
288        loop_(loop) {
289  }
290  virtual ~Deleter() {}
291
292  virtual void OnFileChanged(const FilePath&, bool) OVERRIDE {
293    watcher_.reset();
294    loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure());
295  }
296
297  FilePathWatcher* watcher() const { return watcher_.get(); }
298
299 private:
300  scoped_ptr<FilePathWatcher> watcher_;
301  MessageLoop* loop_;
302
303  DISALLOW_COPY_AND_ASSIGN(Deleter);
304};
305
306// Verify that deleting a watcher during the callback doesn't crash.
307TEST_F(FilePathWatcherTest, DeleteDuringNotify) {
308  FilePathWatcher* watcher = new FilePathWatcher;
309  // Takes ownership of watcher.
310  scoped_ptr<Deleter> deleter(new Deleter(watcher, &loop_));
311  ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get(), false));
312
313  ASSERT_TRUE(WriteFile(test_file(), "content"));
314  ASSERT_TRUE(WaitForEvents());
315
316  // We win if we haven't crashed yet.
317  // Might as well double-check it got deleted, too.
318  ASSERT_TRUE(deleter->watcher() == NULL);
319}
320
321// Verify that deleting the watcher works even if there is a pending
322// notification.
323// Flaky on MacOS (and ARM linux): http://crbug.com/85930
324TEST_F(FilePathWatcherTest, DISABLED_DestroyWithPendingNotification) {
325  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
326  FilePathWatcher* watcher = new FilePathWatcher;
327  ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get(), false));
328  ASSERT_TRUE(WriteFile(test_file(), "content"));
329  file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher);
330  DeleteDelegateOnFileThread(delegate.release());
331}
332
333TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) {
334  FilePathWatcher watcher1, watcher2;
335  scoped_ptr<TestDelegate> delegate1(new TestDelegate(collector()));
336  scoped_ptr<TestDelegate> delegate2(new TestDelegate(collector()));
337  ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get(), false));
338  ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get(), false));
339
340  ASSERT_TRUE(WriteFile(test_file(), "content"));
341  ASSERT_TRUE(WaitForEvents());
342  DeleteDelegateOnFileThread(delegate1.release());
343  DeleteDelegateOnFileThread(delegate2.release());
344}
345
346// Verify that watching a file whose parent directory doesn't exist yet works if
347// the directory and file are created eventually.
348TEST_F(FilePathWatcherTest, NonExistentDirectory) {
349  FilePathWatcher watcher;
350  FilePath dir(temp_dir_.path().AppendASCII("dir"));
351  FilePath file(dir.AppendASCII("file"));
352  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
353  ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false));
354
355  ASSERT_TRUE(file_util::CreateDirectory(dir));
356
357  ASSERT_TRUE(WriteFile(file, "content"));
358
359  VLOG(1) << "Waiting for file creation";
360  ASSERT_TRUE(WaitForEvents());
361
362  ASSERT_TRUE(WriteFile(file, "content v2"));
363  VLOG(1) << "Waiting for file change";
364  ASSERT_TRUE(WaitForEvents());
365
366  ASSERT_TRUE(base::DeleteFile(file, false));
367  VLOG(1) << "Waiting for file deletion";
368  ASSERT_TRUE(WaitForEvents());
369  DeleteDelegateOnFileThread(delegate.release());
370}
371
372// Exercises watch reconfiguration for the case that directories on the path
373// are rapidly created.
374TEST_F(FilePathWatcherTest, DirectoryChain) {
375  FilePath path(temp_dir_.path());
376  std::vector<std::string> dir_names;
377  for (int i = 0; i < 20; i++) {
378    std::string dir(base::StringPrintf("d%d", i));
379    dir_names.push_back(dir);
380    path = path.AppendASCII(dir);
381  }
382
383  FilePathWatcher watcher;
384  FilePath file(path.AppendASCII("file"));
385  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
386  ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false));
387
388  FilePath sub_path(temp_dir_.path());
389  for (std::vector<std::string>::const_iterator d(dir_names.begin());
390       d != dir_names.end(); ++d) {
391    sub_path = sub_path.AppendASCII(*d);
392    ASSERT_TRUE(file_util::CreateDirectory(sub_path));
393  }
394  VLOG(1) << "Create File";
395  ASSERT_TRUE(WriteFile(file, "content"));
396  VLOG(1) << "Waiting for file creation";
397  ASSERT_TRUE(WaitForEvents());
398
399  ASSERT_TRUE(WriteFile(file, "content v2"));
400  VLOG(1) << "Waiting for file modification";
401  ASSERT_TRUE(WaitForEvents());
402  DeleteDelegateOnFileThread(delegate.release());
403}
404
405#if defined(OS_MACOSX)
406// http://crbug.com/85930
407#define DisappearingDirectory DISABLED_DisappearingDirectory
408#endif
409TEST_F(FilePathWatcherTest, DisappearingDirectory) {
410  FilePathWatcher watcher;
411  FilePath dir(temp_dir_.path().AppendASCII("dir"));
412  FilePath file(dir.AppendASCII("file"));
413  ASSERT_TRUE(file_util::CreateDirectory(dir));
414  ASSERT_TRUE(WriteFile(file, "content"));
415  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
416  ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false));
417
418  ASSERT_TRUE(base::DeleteFile(dir, true));
419  ASSERT_TRUE(WaitForEvents());
420  DeleteDelegateOnFileThread(delegate.release());
421}
422
423// Tests that a file that is deleted and reappears is tracked correctly.
424TEST_F(FilePathWatcherTest, DeleteAndRecreate) {
425  ASSERT_TRUE(WriteFile(test_file(), "content"));
426  FilePathWatcher watcher;
427  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
428  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false));
429
430  ASSERT_TRUE(base::DeleteFile(test_file(), false));
431  VLOG(1) << "Waiting for file deletion";
432  ASSERT_TRUE(WaitForEvents());
433
434  ASSERT_TRUE(WriteFile(test_file(), "content"));
435  VLOG(1) << "Waiting for file creation";
436  ASSERT_TRUE(WaitForEvents());
437  DeleteDelegateOnFileThread(delegate.release());
438}
439
440TEST_F(FilePathWatcherTest, WatchDirectory) {
441  FilePathWatcher watcher;
442  FilePath dir(temp_dir_.path().AppendASCII("dir"));
443  FilePath file1(dir.AppendASCII("file1"));
444  FilePath file2(dir.AppendASCII("file2"));
445  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
446  ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), false));
447
448  ASSERT_TRUE(file_util::CreateDirectory(dir));
449  VLOG(1) << "Waiting for directory creation";
450  ASSERT_TRUE(WaitForEvents());
451
452  ASSERT_TRUE(WriteFile(file1, "content"));
453  VLOG(1) << "Waiting for file1 creation";
454  ASSERT_TRUE(WaitForEvents());
455
456#if !defined(OS_MACOSX)
457  // Mac implementation does not detect files modified in a directory.
458  ASSERT_TRUE(WriteFile(file1, "content v2"));
459  VLOG(1) << "Waiting for file1 modification";
460  ASSERT_TRUE(WaitForEvents());
461#endif  // !OS_MACOSX
462
463  ASSERT_TRUE(base::DeleteFile(file1, false));
464  VLOG(1) << "Waiting for file1 deletion";
465  ASSERT_TRUE(WaitForEvents());
466
467  ASSERT_TRUE(WriteFile(file2, "content"));
468  VLOG(1) << "Waiting for file2 creation";
469  ASSERT_TRUE(WaitForEvents());
470  DeleteDelegateOnFileThread(delegate.release());
471}
472
473TEST_F(FilePathWatcherTest, MoveParent) {
474  FilePathWatcher file_watcher;
475  FilePathWatcher subdir_watcher;
476  FilePath dir(temp_dir_.path().AppendASCII("dir"));
477  FilePath dest(temp_dir_.path().AppendASCII("dest"));
478  FilePath subdir(dir.AppendASCII("subdir"));
479  FilePath file(subdir.AppendASCII("file"));
480  scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector()));
481  ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get(), false));
482  scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector()));
483  ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get(),
484                         false));
485
486  // Setup a directory hierarchy.
487  ASSERT_TRUE(file_util::CreateDirectory(subdir));
488  ASSERT_TRUE(WriteFile(file, "content"));
489  VLOG(1) << "Waiting for file creation";
490  ASSERT_TRUE(WaitForEvents());
491
492  // Move the parent directory.
493  base::Move(dir, dest);
494  VLOG(1) << "Waiting for directory move";
495  ASSERT_TRUE(WaitForEvents());
496  DeleteDelegateOnFileThread(file_delegate.release());
497  DeleteDelegateOnFileThread(subdir_delegate.release());
498}
499
500#if defined(OS_WIN)
501TEST_F(FilePathWatcherTest, RecursiveWatch) {
502  FilePathWatcher watcher;
503  FilePath dir(temp_dir_.path().AppendASCII("dir"));
504  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
505  ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), true));
506
507  // Main directory("dir") creation.
508  ASSERT_TRUE(file_util::CreateDirectory(dir));
509  ASSERT_TRUE(WaitForEvents());
510
511  // Create "$dir/file1".
512  FilePath file1(dir.AppendASCII("file1"));
513  ASSERT_TRUE(WriteFile(file1, "content"));
514  ASSERT_TRUE(WaitForEvents());
515
516  // Create "$dir/subdir".
517  FilePath subdir(dir.AppendASCII("subdir"));
518  ASSERT_TRUE(file_util::CreateDirectory(subdir));
519  ASSERT_TRUE(WaitForEvents());
520
521  // Create "$dir/subdir/subdir_file1".
522  FilePath subdir_file1(subdir.AppendASCII("subdir_file1"));
523  ASSERT_TRUE(WriteFile(subdir_file1, "content"));
524  ASSERT_TRUE(WaitForEvents());
525
526  // Create "$dir/subdir/subdir_child_dir".
527  FilePath subdir_child_dir(subdir.AppendASCII("subdir_child_dir"));
528  ASSERT_TRUE(file_util::CreateDirectory(subdir_child_dir));
529  ASSERT_TRUE(WaitForEvents());
530
531  // Create "$dir/subdir/subdir_child_dir/child_dir_file1".
532  FilePath child_dir_file1(subdir_child_dir.AppendASCII("child_dir_file1"));
533  ASSERT_TRUE(WriteFile(child_dir_file1, "content v2"));
534  ASSERT_TRUE(WaitForEvents());
535
536  // Write into "$dir/subdir/subdir_child_dir/child_dir_file1".
537  ASSERT_TRUE(WriteFile(child_dir_file1, "content"));
538  ASSERT_TRUE(WaitForEvents());
539
540  // Modify "$dir/subdir/subdir_child_dir/child_dir_file1" attributes.
541  ASSERT_TRUE(file_util::MakeFileUnreadable(child_dir_file1));
542  ASSERT_TRUE(WaitForEvents());
543
544  // Delete "$dir/subdir/subdir_file1".
545  ASSERT_TRUE(base::DeleteFile(subdir_file1, false));
546  ASSERT_TRUE(WaitForEvents());
547
548  // Delete "$dir/subdir/subdir_child_dir/child_dir_file1".
549  ASSERT_TRUE(base::DeleteFile(child_dir_file1, false));
550  ASSERT_TRUE(WaitForEvents());
551  DeleteDelegateOnFileThread(delegate.release());
552}
553#else
554TEST_F(FilePathWatcherTest, RecursiveWatch) {
555  FilePathWatcher watcher;
556  FilePath dir(temp_dir_.path().AppendASCII("dir"));
557  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
558  // Non-Windows implementaion does not support recursive watching.
559  ASSERT_FALSE(SetupWatch(dir, &watcher, delegate.get(), true));
560  DeleteDelegateOnFileThread(delegate.release());
561}
562#endif
563
564TEST_F(FilePathWatcherTest, MoveChild) {
565  FilePathWatcher file_watcher;
566  FilePathWatcher subdir_watcher;
567  FilePath source_dir(temp_dir_.path().AppendASCII("source"));
568  FilePath source_subdir(source_dir.AppendASCII("subdir"));
569  FilePath source_file(source_subdir.AppendASCII("file"));
570  FilePath dest_dir(temp_dir_.path().AppendASCII("dest"));
571  FilePath dest_subdir(dest_dir.AppendASCII("subdir"));
572  FilePath dest_file(dest_subdir.AppendASCII("file"));
573
574  // Setup a directory hierarchy.
575  ASSERT_TRUE(file_util::CreateDirectory(source_subdir));
576  ASSERT_TRUE(WriteFile(source_file, "content"));
577
578  scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector()));
579  ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get(), false));
580  scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector()));
581  ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get(),
582                         false));
583
584  // Move the directory into place, s.t. the watched file appears.
585  ASSERT_TRUE(base::Move(source_dir, dest_dir));
586  ASSERT_TRUE(WaitForEvents());
587  DeleteDelegateOnFileThread(file_delegate.release());
588  DeleteDelegateOnFileThread(subdir_delegate.release());
589}
590
591#if !defined(OS_LINUX)
592// Linux implementation of FilePathWatcher doesn't catch attribute changes.
593// http://crbug.com/78043
594
595// Verify that changing attributes on a file is caught
596TEST_F(FilePathWatcherTest, FileAttributesChanged) {
597  ASSERT_TRUE(WriteFile(test_file(), "content"));
598  FilePathWatcher watcher;
599  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
600  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false));
601
602  // Now make sure we get notified if the file is modified.
603  ASSERT_TRUE(file_util::MakeFileUnreadable(test_file()));
604  ASSERT_TRUE(WaitForEvents());
605  DeleteDelegateOnFileThread(delegate.release());
606}
607
608#endif  // !OS_LINUX
609
610#if defined(OS_LINUX)
611
612// Verify that creating a symlink is caught.
613TEST_F(FilePathWatcherTest, CreateLink) {
614  FilePathWatcher watcher;
615  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
616  // Note that we are watching the symlink
617  ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false));
618
619  // Now make sure we get notified if the link is created.
620  // Note that test_file() doesn't have to exist.
621  ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link()));
622  ASSERT_TRUE(WaitForEvents());
623  DeleteDelegateOnFileThread(delegate.release());
624}
625
626// Verify that deleting a symlink is caught.
627TEST_F(FilePathWatcherTest, DeleteLink) {
628  // Unfortunately this test case only works if the link target exists.
629  // TODO(craig) fix this as part of crbug.com/91561.
630  ASSERT_TRUE(WriteFile(test_file(), "content"));
631  ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link()));
632  FilePathWatcher watcher;
633  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
634  ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false));
635
636  // Now make sure we get notified if the link is deleted.
637  ASSERT_TRUE(base::DeleteFile(test_link(), false));
638  ASSERT_TRUE(WaitForEvents());
639  DeleteDelegateOnFileThread(delegate.release());
640}
641
642// Verify that modifying a target file that a link is pointing to
643// when we are watching the link is caught.
644TEST_F(FilePathWatcherTest, ModifiedLinkedFile) {
645  ASSERT_TRUE(WriteFile(test_file(), "content"));
646  ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link()));
647  FilePathWatcher watcher;
648  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
649  // Note that we are watching the symlink.
650  ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false));
651
652  // Now make sure we get notified if the file is modified.
653  ASSERT_TRUE(WriteFile(test_file(), "new content"));
654  ASSERT_TRUE(WaitForEvents());
655  DeleteDelegateOnFileThread(delegate.release());
656}
657
658// Verify that creating a target file that a link is pointing to
659// when we are watching the link is caught.
660TEST_F(FilePathWatcherTest, CreateTargetLinkedFile) {
661  ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link()));
662  FilePathWatcher watcher;
663  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
664  // Note that we are watching the symlink.
665  ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false));
666
667  // Now make sure we get notified if the target file is created.
668  ASSERT_TRUE(WriteFile(test_file(), "content"));
669  ASSERT_TRUE(WaitForEvents());
670  DeleteDelegateOnFileThread(delegate.release());
671}
672
673// Verify that deleting a target file that a link is pointing to
674// when we are watching the link is caught.
675TEST_F(FilePathWatcherTest, DeleteTargetLinkedFile) {
676  ASSERT_TRUE(WriteFile(test_file(), "content"));
677  ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link()));
678  FilePathWatcher watcher;
679  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
680  // Note that we are watching the symlink.
681  ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false));
682
683  // Now make sure we get notified if the target file is deleted.
684  ASSERT_TRUE(base::DeleteFile(test_file(), false));
685  ASSERT_TRUE(WaitForEvents());
686  DeleteDelegateOnFileThread(delegate.release());
687}
688
689// Verify that watching a file whose parent directory is a link that
690// doesn't exist yet works if the symlink is created eventually.
691TEST_F(FilePathWatcherTest, LinkedDirectoryPart1) {
692  FilePathWatcher watcher;
693  FilePath dir(temp_dir_.path().AppendASCII("dir"));
694  FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk"));
695  FilePath file(dir.AppendASCII("file"));
696  FilePath linkfile(link_dir.AppendASCII("file"));
697  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
698  // dir/file should exist.
699  ASSERT_TRUE(file_util::CreateDirectory(dir));
700  ASSERT_TRUE(WriteFile(file, "content"));
701  // Note that we are watching dir.lnk/file which doesn't exist yet.
702  ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false));
703
704  ASSERT_TRUE(file_util::CreateSymbolicLink(dir, link_dir));
705  VLOG(1) << "Waiting for link creation";
706  ASSERT_TRUE(WaitForEvents());
707
708  ASSERT_TRUE(WriteFile(file, "content v2"));
709  VLOG(1) << "Waiting for file change";
710  ASSERT_TRUE(WaitForEvents());
711
712  ASSERT_TRUE(base::DeleteFile(file, false));
713  VLOG(1) << "Waiting for file deletion";
714  ASSERT_TRUE(WaitForEvents());
715  DeleteDelegateOnFileThread(delegate.release());
716}
717
718// Verify that watching a file whose parent directory is a
719// dangling symlink works if the directory is created eventually.
720TEST_F(FilePathWatcherTest, LinkedDirectoryPart2) {
721  FilePathWatcher watcher;
722  FilePath dir(temp_dir_.path().AppendASCII("dir"));
723  FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk"));
724  FilePath file(dir.AppendASCII("file"));
725  FilePath linkfile(link_dir.AppendASCII("file"));
726  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
727  // Now create the link from dir.lnk pointing to dir but
728  // neither dir nor dir/file exist yet.
729  ASSERT_TRUE(file_util::CreateSymbolicLink(dir, link_dir));
730  // Note that we are watching dir.lnk/file.
731  ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false));
732
733  ASSERT_TRUE(file_util::CreateDirectory(dir));
734  ASSERT_TRUE(WriteFile(file, "content"));
735  VLOG(1) << "Waiting for dir/file creation";
736  ASSERT_TRUE(WaitForEvents());
737
738  ASSERT_TRUE(WriteFile(file, "content v2"));
739  VLOG(1) << "Waiting for file change";
740  ASSERT_TRUE(WaitForEvents());
741
742  ASSERT_TRUE(base::DeleteFile(file, false));
743  VLOG(1) << "Waiting for file deletion";
744  ASSERT_TRUE(WaitForEvents());
745  DeleteDelegateOnFileThread(delegate.release());
746}
747
748// Verify that watching a file with a symlink on the path
749// to the file works.
750TEST_F(FilePathWatcherTest, LinkedDirectoryPart3) {
751  FilePathWatcher watcher;
752  FilePath dir(temp_dir_.path().AppendASCII("dir"));
753  FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk"));
754  FilePath file(dir.AppendASCII("file"));
755  FilePath linkfile(link_dir.AppendASCII("file"));
756  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
757  ASSERT_TRUE(file_util::CreateDirectory(dir));
758  ASSERT_TRUE(file_util::CreateSymbolicLink(dir, link_dir));
759  // Note that we are watching dir.lnk/file but the file doesn't exist yet.
760  ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false));
761
762  ASSERT_TRUE(WriteFile(file, "content"));
763  VLOG(1) << "Waiting for file creation";
764  ASSERT_TRUE(WaitForEvents());
765
766  ASSERT_TRUE(WriteFile(file, "content v2"));
767  VLOG(1) << "Waiting for file change";
768  ASSERT_TRUE(WaitForEvents());
769
770  ASSERT_TRUE(base::DeleteFile(file, false));
771  VLOG(1) << "Waiting for file deletion";
772  ASSERT_TRUE(WaitForEvents());
773  DeleteDelegateOnFileThread(delegate.release());
774}
775
776#endif  // OS_LINUX
777
778enum Permission {
779  Read,
780  Write,
781  Execute
782};
783
784bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) {
785#if defined(OS_POSIX)
786  struct stat stat_buf;
787
788  if (stat(path.value().c_str(), &stat_buf) != 0)
789    return false;
790
791  mode_t mode = 0;
792  switch (perm) {
793    case Read:
794      mode = S_IRUSR | S_IRGRP | S_IROTH;
795      break;
796    case Write:
797      mode = S_IWUSR | S_IWGRP | S_IWOTH;
798      break;
799    case Execute:
800      mode = S_IXUSR | S_IXGRP | S_IXOTH;
801      break;
802    default:
803      ADD_FAILURE() << "unknown perm " << perm;
804      return false;
805  }
806  if (allow) {
807    stat_buf.st_mode |= mode;
808  } else {
809    stat_buf.st_mode &= ~mode;
810  }
811  return chmod(path.value().c_str(), stat_buf.st_mode) == 0;
812
813#elif defined(OS_WIN)
814  PACL old_dacl;
815  PSECURITY_DESCRIPTOR security_descriptor;
816  if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
817                           SE_FILE_OBJECT,
818                           DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl,
819                           NULL, &security_descriptor) != ERROR_SUCCESS)
820    return false;
821
822  DWORD mode = 0;
823  switch (perm) {
824    case Read:
825      mode = GENERIC_READ;
826      break;
827    case Write:
828      mode = GENERIC_WRITE;
829      break;
830    case Execute:
831      mode = GENERIC_EXECUTE;
832      break;
833    default:
834      ADD_FAILURE() << "unknown perm " << perm;
835      return false;
836  }
837
838  // Deny Read access for the current user.
839  EXPLICIT_ACCESS change;
840  change.grfAccessPermissions = mode;
841  change.grfAccessMode = allow ? GRANT_ACCESS : DENY_ACCESS;
842  change.grfInheritance = 0;
843  change.Trustee.pMultipleTrustee = NULL;
844  change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
845  change.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
846  change.Trustee.TrusteeType = TRUSTEE_IS_USER;
847  change.Trustee.ptstrName = L"CURRENT_USER";
848
849  PACL new_dacl;
850  if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) {
851    LocalFree(security_descriptor);
852    return false;
853  }
854
855  DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
856                                  SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
857                                  NULL, NULL, new_dacl, NULL);
858  LocalFree(security_descriptor);
859  LocalFree(new_dacl);
860
861  return rc == ERROR_SUCCESS;
862#else
863  NOTIMPLEMENTED();
864  return false;
865#endif
866}
867
868#if defined(OS_MACOSX)
869// Linux implementation of FilePathWatcher doesn't catch attribute changes.
870// http://crbug.com/78043
871// Windows implementation of FilePathWatcher catches attribute changes that
872// don't affect the path being watched.
873// http://crbug.com/78045
874
875// Verify that changing attributes on a directory works.
876TEST_F(FilePathWatcherTest, DirAttributesChanged) {
877  FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1"));
878  FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2"));
879  FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile"));
880  // Setup a directory hierarchy.
881  ASSERT_TRUE(file_util::CreateDirectory(test_dir1));
882  ASSERT_TRUE(file_util::CreateDirectory(test_dir2));
883  ASSERT_TRUE(WriteFile(test_file, "content"));
884
885  FilePathWatcher watcher;
886  scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
887  ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get(), false));
888
889  // We should not get notified in this case as it hasn't affected our ability
890  // to access the file.
891  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false));
892  loop_.PostDelayedTask(FROM_HERE,
893                        MessageLoop::QuitWhenIdleClosure(),
894                        TestTimeouts::tiny_timeout());
895  ASSERT_FALSE(WaitForEvents());
896  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true));
897
898  // We should get notified in this case because filepathwatcher can no
899  // longer access the file
900  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false));
901  ASSERT_TRUE(WaitForEvents());
902  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true));
903  DeleteDelegateOnFileThread(delegate.release());
904}
905
906#endif  // OS_MACOSX
907}  // namespace
908
909}  // namespace base
910