Home | History | Annotate | Download | only in files
      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 <set>
      8 
      9 #if defined(OS_WIN)
     10 #include <windows.h>
     11 #include <aclapi.h>
     12 #elif defined(OS_POSIX)
     13 #include <sys/stat.h>
     14 #endif
     15 
     16 #include "base/basictypes.h"
     17 #include "base/compiler_specific.h"
     18 #include "base/file_path.h"
     19 #include "base/file_util.h"
     20 #include "base/memory/scoped_temp_dir.h"
     21 #include "base/message_loop.h"
     22 #include "base/message_loop_proxy.h"
     23 #include "base/path_service.h"
     24 #include "base/string_util.h"
     25 #include "base/stl_util-inl.h"
     26 #include "base/synchronization/waitable_event.h"
     27 #include "base/test/test_timeouts.h"
     28 #include "base/threading/thread.h"
     29 #include "testing/gtest/include/gtest/gtest.h"
     30 
     31 namespace base {
     32 namespace files {
     33 
     34 namespace {
     35 
     36 class TestDelegate;
     37 
     38 // Aggregates notifications from the test delegates and breaks the message loop
     39 // the test thread is waiting on once they all came in.
     40 class NotificationCollector
     41     : public base::RefCountedThreadSafe<NotificationCollector> {
     42  public:
     43   NotificationCollector()
     44       : loop_(base::MessageLoopProxy::CreateForCurrentThread()) {}
     45 
     46   // Called from the file thread by the delegates.
     47   void OnChange(TestDelegate* delegate) {
     48     loop_->PostTask(FROM_HERE,
     49                     NewRunnableMethod(this,
     50                                       &NotificationCollector::RecordChange,
     51                                       make_scoped_refptr(delegate)));
     52   }
     53 
     54   void Register(TestDelegate* delegate) {
     55     delegates_.insert(delegate);
     56   }
     57 
     58   void Reset() {
     59     signaled_.clear();
     60   }
     61 
     62   bool Success() {
     63     return signaled_ == delegates_;
     64   }
     65 
     66  private:
     67   void RecordChange(TestDelegate* delegate) {
     68     ASSERT_TRUE(loop_->BelongsToCurrentThread());
     69     ASSERT_TRUE(delegates_.count(delegate));
     70     signaled_.insert(delegate);
     71 
     72     // Check whether all delegates have been signaled.
     73     if (signaled_ == delegates_)
     74       loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
     75   }
     76 
     77   // Set of registered delegates.
     78   std::set<TestDelegate*> delegates_;
     79 
     80   // Set of signaled delegates.
     81   std::set<TestDelegate*> signaled_;
     82 
     83   // The loop we should break after all delegates signaled.
     84   scoped_refptr<base::MessageLoopProxy> loop_;
     85 };
     86 
     87 // A mock FilePathWatcher::Delegate for testing. I'd rather use gmock, but it's
     88 // not thread safe for setting expectations, so the test code couldn't safely
     89 // reset expectations while the file watcher is running. In order to allow this,
     90 // we keep simple thread safe status flags in TestDelegate.
     91 class TestDelegate : public FilePathWatcher::Delegate {
     92  public:
     93   // The message loop specified by |loop| will be quit if a notification is
     94   // received while the delegate is |armed_|. Note that the testing code must
     95   // guarantee |loop| outlives the file thread on which OnFilePathChanged runs.
     96   explicit TestDelegate(NotificationCollector* collector)
     97       : collector_(collector) {
     98     collector_->Register(this);
     99   }
    100 
    101   virtual void OnFilePathChanged(const FilePath&) {
    102     collector_->OnChange(this);
    103   }
    104 
    105   virtual void OnFilePathError(const FilePath& path) {
    106     ADD_FAILURE() << "Error " << path.value();
    107   }
    108 
    109  private:
    110   scoped_refptr<NotificationCollector> collector_;
    111 
    112   DISALLOW_COPY_AND_ASSIGN(TestDelegate);
    113 };
    114 
    115 // A helper class for setting up watches on the file thread.
    116 class SetupWatchTask : public Task {
    117  public:
    118   SetupWatchTask(const FilePath& target,
    119                  FilePathWatcher* watcher,
    120                  FilePathWatcher::Delegate* delegate,
    121                  bool* result,
    122                  base::WaitableEvent* completion)
    123       : target_(target),
    124         watcher_(watcher),
    125         delegate_(delegate),
    126         result_(result),
    127         completion_(completion) {}
    128 
    129   void Run() {
    130     *result_ = watcher_->Watch(target_, delegate_);
    131     completion_->Signal();
    132   }
    133 
    134  private:
    135   const FilePath target_;
    136   FilePathWatcher* watcher_;
    137   FilePathWatcher::Delegate* delegate_;
    138   bool* result_;
    139   base::WaitableEvent* completion_;
    140 
    141   DISALLOW_COPY_AND_ASSIGN(SetupWatchTask);
    142 };
    143 
    144 class FilePathWatcherTest : public testing::Test {
    145  public:
    146   FilePathWatcherTest()
    147       : file_thread_("FilePathWatcherTest") {}
    148 
    149   virtual ~FilePathWatcherTest() {}
    150 
    151  protected:
    152   virtual void SetUp() {
    153     // Create a separate file thread in order to test proper thread usage.
    154     base::Thread::Options options(MessageLoop::TYPE_IO, 0);
    155     ASSERT_TRUE(file_thread_.StartWithOptions(options));
    156     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    157     collector_ = new NotificationCollector();
    158   }
    159 
    160   virtual void TearDown() {
    161     loop_.RunAllPending();
    162   }
    163 
    164   FilePath test_file() {
    165     return temp_dir_.path().AppendASCII("FilePathWatcherTest");
    166   }
    167 
    168   // Write |content| to |file|. Returns true on success.
    169   bool WriteFile(const FilePath& file, const std::string& content) {
    170     int write_size = file_util::WriteFile(file, content.c_str(),
    171                                           content.length());
    172     return write_size == static_cast<int>(content.length());
    173   }
    174 
    175   bool SetupWatch(const FilePath& target,
    176                   FilePathWatcher* watcher,
    177                   FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT {
    178     base::WaitableEvent completion(false, false);
    179     bool result;
    180     file_thread_.message_loop_proxy()->PostTask(FROM_HERE,
    181          new SetupWatchTask(target,
    182                             watcher,
    183                             delegate,
    184                             &result,
    185                             &completion));
    186     completion.Wait();
    187     return result;
    188   }
    189 
    190   bool WaitForEvents() WARN_UNUSED_RESULT {
    191     collector_->Reset();
    192     loop_.Run();
    193     return collector_->Success();
    194   }
    195 
    196   NotificationCollector* collector() { return collector_.get(); }
    197 
    198   MessageLoop loop_;
    199   base::Thread file_thread_;
    200   ScopedTempDir temp_dir_;
    201   scoped_refptr<NotificationCollector> collector_;
    202 };
    203 
    204 // Basic test: Create the file and verify that we notice.
    205 TEST_F(FilePathWatcherTest, NewFile) {
    206   FilePathWatcher watcher;
    207   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    208   ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
    209 
    210   ASSERT_TRUE(WriteFile(test_file(), "content"));
    211   ASSERT_TRUE(WaitForEvents());
    212 }
    213 
    214 // Verify that modifying the file is caught.
    215 TEST_F(FilePathWatcherTest, ModifiedFile) {
    216   ASSERT_TRUE(WriteFile(test_file(), "content"));
    217 
    218   FilePathWatcher watcher;
    219   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    220   ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
    221 
    222   // Now make sure we get notified if the file is modified.
    223   ASSERT_TRUE(WriteFile(test_file(), "new content"));
    224   ASSERT_TRUE(WaitForEvents());
    225 }
    226 
    227 // Verify that moving the file into place is caught.
    228 TEST_F(FilePathWatcherTest, MovedFile) {
    229   FilePath source_file(temp_dir_.path().AppendASCII("source"));
    230   ASSERT_TRUE(WriteFile(source_file, "content"));
    231 
    232   FilePathWatcher watcher;
    233   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    234   ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
    235 
    236   // Now make sure we get notified if the file is modified.
    237   ASSERT_TRUE(file_util::Move(source_file, test_file()));
    238   ASSERT_TRUE(WaitForEvents());
    239 }
    240 
    241 TEST_F(FilePathWatcherTest, DeletedFile) {
    242   ASSERT_TRUE(WriteFile(test_file(), "content"));
    243 
    244   FilePathWatcher watcher;
    245   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    246   ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
    247 
    248   // Now make sure we get notified if the file is deleted.
    249   file_util::Delete(test_file(), false);
    250   ASSERT_TRUE(WaitForEvents());
    251 }
    252 
    253 // Used by the DeleteDuringNotify test below.
    254 // Deletes the FilePathWatcher when it's notified.
    255 class Deleter : public FilePathWatcher::Delegate {
    256  public:
    257   Deleter(FilePathWatcher* watcher, MessageLoop* loop)
    258       : watcher_(watcher),
    259         loop_(loop) {
    260   }
    261 
    262   virtual void OnFilePathChanged(const FilePath& path) {
    263     watcher_.reset();
    264     loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
    265   }
    266 
    267   scoped_ptr<FilePathWatcher> watcher_;
    268   MessageLoop* loop_;
    269 };
    270 
    271 // Verify that deleting a watcher during the callback doesn't crash.
    272 TEST_F(FilePathWatcherTest, DeleteDuringNotify) {
    273   FilePathWatcher* watcher = new FilePathWatcher;
    274   // Takes ownership of watcher.
    275   scoped_refptr<Deleter> deleter(new Deleter(watcher, &loop_));
    276   ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get()));
    277 
    278   ASSERT_TRUE(WriteFile(test_file(), "content"));
    279   ASSERT_TRUE(WaitForEvents());
    280 
    281   // We win if we haven't crashed yet.
    282   // Might as well double-check it got deleted, too.
    283   ASSERT_TRUE(deleter->watcher_.get() == NULL);
    284 }
    285 
    286 // Verify that deleting the watcher works even if there is a pending
    287 // notification.
    288 TEST_F(FilePathWatcherTest, DestroyWithPendingNotification) {
    289   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    290   FilePathWatcher* watcher = new FilePathWatcher;
    291   ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get()));
    292   ASSERT_TRUE(WriteFile(test_file(), "content"));
    293   file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher);
    294 }
    295 
    296 TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) {
    297   FilePathWatcher watcher1, watcher2;
    298   scoped_refptr<TestDelegate> delegate1(new TestDelegate(collector()));
    299   scoped_refptr<TestDelegate> delegate2(new TestDelegate(collector()));
    300   ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get()));
    301   ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get()));
    302 
    303   ASSERT_TRUE(WriteFile(test_file(), "content"));
    304   ASSERT_TRUE(WaitForEvents());
    305 }
    306 
    307 // Verify that watching a file whose parent directory doesn't exist yet works if
    308 // the directory and file are created eventually.
    309 TEST_F(FilePathWatcherTest, NonExistentDirectory) {
    310   FilePathWatcher watcher;
    311   FilePath dir(temp_dir_.path().AppendASCII("dir"));
    312   FilePath file(dir.AppendASCII("file"));
    313   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    314   ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get()));
    315 
    316   ASSERT_TRUE(file_util::CreateDirectory(dir));
    317 
    318   ASSERT_TRUE(WriteFile(file, "content"));
    319 
    320   VLOG(1) << "Waiting for file creation";
    321   ASSERT_TRUE(WaitForEvents());
    322 
    323   ASSERT_TRUE(WriteFile(file, "content v2"));
    324   VLOG(1) << "Waiting for file change";
    325   ASSERT_TRUE(WaitForEvents());
    326 
    327   ASSERT_TRUE(file_util::Delete(file, false));
    328   VLOG(1) << "Waiting for file deletion";
    329   ASSERT_TRUE(WaitForEvents());
    330 }
    331 
    332 // Exercises watch reconfiguration for the case that directories on the path
    333 // are rapidly created.
    334 TEST_F(FilePathWatcherTest, DirectoryChain) {
    335   FilePath path(temp_dir_.path());
    336   std::vector<std::string> dir_names;
    337   for (int i = 0; i < 20; i++) {
    338     std::string dir(StringPrintf("d%d", i));
    339     dir_names.push_back(dir);
    340     path = path.AppendASCII(dir);
    341   }
    342 
    343   FilePathWatcher watcher;
    344   FilePath file(path.AppendASCII("file"));
    345   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    346   ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get()));
    347 
    348   FilePath sub_path(temp_dir_.path());
    349   for (std::vector<std::string>::const_iterator d(dir_names.begin());
    350        d != dir_names.end(); ++d) {
    351     sub_path = sub_path.AppendASCII(*d);
    352     ASSERT_TRUE(file_util::CreateDirectory(sub_path));
    353   }
    354   VLOG(1) << "Create File";
    355   ASSERT_TRUE(WriteFile(file, "content"));
    356   VLOG(1) << "Waiting for file creation";
    357   ASSERT_TRUE(WaitForEvents());
    358 
    359   ASSERT_TRUE(WriteFile(file, "content v2"));
    360   VLOG(1) << "Waiting for file modification";
    361   ASSERT_TRUE(WaitForEvents());
    362 }
    363 
    364 TEST_F(FilePathWatcherTest, DisappearingDirectory) {
    365   FilePathWatcher watcher;
    366   FilePath dir(temp_dir_.path().AppendASCII("dir"));
    367   FilePath file(dir.AppendASCII("file"));
    368   ASSERT_TRUE(file_util::CreateDirectory(dir));
    369   ASSERT_TRUE(WriteFile(file, "content"));
    370   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    371   ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get()));
    372 
    373   ASSERT_TRUE(file_util::Delete(dir, true));
    374   ASSERT_TRUE(WaitForEvents());
    375 }
    376 
    377 // Tests that a file that is deleted and reappears is tracked correctly.
    378 TEST_F(FilePathWatcherTest, DeleteAndRecreate) {
    379   ASSERT_TRUE(WriteFile(test_file(), "content"));
    380   FilePathWatcher watcher;
    381   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    382   ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
    383 
    384   ASSERT_TRUE(file_util::Delete(test_file(), false));
    385   VLOG(1) << "Waiting for file deletion";
    386   ASSERT_TRUE(WaitForEvents());
    387 
    388   ASSERT_TRUE(WriteFile(test_file(), "content"));
    389   VLOG(1) << "Waiting for file creation";
    390   ASSERT_TRUE(WaitForEvents());
    391 }
    392 
    393 TEST_F(FilePathWatcherTest, WatchDirectory) {
    394   FilePathWatcher watcher;
    395   FilePath dir(temp_dir_.path().AppendASCII("dir"));
    396   FilePath file1(dir.AppendASCII("file1"));
    397   FilePath file2(dir.AppendASCII("file2"));
    398   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    399   ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get()));
    400 
    401   ASSERT_TRUE(file_util::CreateDirectory(dir));
    402   VLOG(1) << "Waiting for directory creation";
    403   ASSERT_TRUE(WaitForEvents());
    404 
    405   ASSERT_TRUE(WriteFile(file1, "content"));
    406   VLOG(1) << "Waiting for file1 creation";
    407   ASSERT_TRUE(WaitForEvents());
    408 
    409 #if !defined(OS_MACOSX)
    410   // Mac implementation does not detect files modified in a directory.
    411   ASSERT_TRUE(WriteFile(file1, "content v2"));
    412   VLOG(1) << "Waiting for file1 modification";
    413   ASSERT_TRUE(WaitForEvents());
    414 #endif  // !OS_MACOSX
    415 
    416   ASSERT_TRUE(file_util::Delete(file1, false));
    417   VLOG(1) << "Waiting for file1 deletion";
    418   ASSERT_TRUE(WaitForEvents());
    419 
    420   ASSERT_TRUE(WriteFile(file2, "content"));
    421   VLOG(1) << "Waiting for file2 creation";
    422   ASSERT_TRUE(WaitForEvents());
    423 }
    424 
    425 TEST_F(FilePathWatcherTest, MoveParent) {
    426   FilePathWatcher file_watcher;
    427   FilePathWatcher subdir_watcher;
    428   FilePath dir(temp_dir_.path().AppendASCII("dir"));
    429   FilePath dest(temp_dir_.path().AppendASCII("dest"));
    430   FilePath subdir(dir.AppendASCII("subdir"));
    431   FilePath file(subdir.AppendASCII("file"));
    432   scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector()));
    433   ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get()));
    434   scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector()));
    435   ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get()));
    436 
    437   // Setup a directory hierarchy.
    438   ASSERT_TRUE(file_util::CreateDirectory(subdir));
    439   ASSERT_TRUE(WriteFile(file, "content"));
    440   VLOG(1) << "Waiting for file creation";
    441   ASSERT_TRUE(WaitForEvents());
    442 
    443   // Move the parent directory.
    444   file_util::Move(dir, dest);
    445   VLOG(1) << "Waiting for directory move";
    446   ASSERT_TRUE(WaitForEvents());
    447 }
    448 
    449 TEST_F(FilePathWatcherTest, MoveChild) {
    450   FilePathWatcher file_watcher;
    451   FilePathWatcher subdir_watcher;
    452   FilePath source_dir(temp_dir_.path().AppendASCII("source"));
    453   FilePath source_subdir(source_dir.AppendASCII("subdir"));
    454   FilePath source_file(source_subdir.AppendASCII("file"));
    455   FilePath dest_dir(temp_dir_.path().AppendASCII("dest"));
    456   FilePath dest_subdir(dest_dir.AppendASCII("subdir"));
    457   FilePath dest_file(dest_subdir.AppendASCII("file"));
    458 
    459   // Setup a directory hierarchy.
    460   ASSERT_TRUE(file_util::CreateDirectory(source_subdir));
    461   ASSERT_TRUE(WriteFile(source_file, "content"));
    462 
    463   scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector()));
    464   ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get()));
    465   scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector()));
    466   ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get()));
    467 
    468   // Move the directory into place, s.t. the watched file appears.
    469   ASSERT_TRUE(file_util::Move(source_dir, dest_dir));
    470   ASSERT_TRUE(WaitForEvents());
    471 }
    472 
    473 #if !defined(OS_LINUX)
    474 // Linux implementation of FilePathWatcher doesn't catch attribute changes.
    475 // http://crbug.com/78043
    476 
    477 // Verify that changing attributes on a file is caught
    478 TEST_F(FilePathWatcherTest, FileAttributesChanged) {
    479   ASSERT_TRUE(WriteFile(test_file(), "content"));
    480   FilePathWatcher watcher;
    481   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    482   ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
    483 
    484   // Now make sure we get notified if the file is modified.
    485   ASSERT_TRUE(file_util::MakeFileUnreadable(test_file()));
    486   ASSERT_TRUE(WaitForEvents());
    487 }
    488 
    489 #endif  // !OS_LINUX
    490 
    491 enum Permission {
    492   Read,
    493   Write,
    494   Execute
    495 };
    496 
    497 bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) {
    498 #if defined(OS_POSIX)
    499   struct stat stat_buf;
    500 
    501   if (stat(path.value().c_str(), &stat_buf) != 0)
    502     return false;
    503 
    504   mode_t mode = 0;
    505   switch (perm) {
    506     case Read:
    507       mode = S_IRUSR | S_IRGRP | S_IROTH;
    508       break;
    509     case Write:
    510       mode = S_IWUSR | S_IWGRP | S_IWOTH;
    511       break;
    512     case Execute:
    513       mode = S_IXUSR | S_IXGRP | S_IXOTH;
    514       break;
    515     default:
    516       ADD_FAILURE() << "unknown perm " << perm;
    517       return false;
    518   }
    519   if (allow) {
    520     stat_buf.st_mode |= mode;
    521   } else {
    522     stat_buf.st_mode &= ~mode;
    523   }
    524   return chmod(path.value().c_str(), stat_buf.st_mode) == 0;
    525 
    526 #elif defined(OS_WIN)
    527   PACL old_dacl;
    528   PSECURITY_DESCRIPTOR security_descriptor;
    529   if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
    530                            SE_FILE_OBJECT,
    531                            DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl,
    532                            NULL, &security_descriptor) != ERROR_SUCCESS)
    533     return false;
    534 
    535   DWORD mode = 0;
    536   switch (perm) {
    537     case Read:
    538       mode = GENERIC_READ;
    539       break;
    540     case Write:
    541       mode = GENERIC_WRITE;
    542       break;
    543     case Execute:
    544       mode = GENERIC_EXECUTE;
    545       break;
    546     default:
    547       ADD_FAILURE() << "unknown perm " << perm;
    548       return false;
    549   }
    550 
    551   // Deny Read access for the current user.
    552   EXPLICIT_ACCESS change;
    553   change.grfAccessPermissions = mode;
    554   change.grfAccessMode = allow ? GRANT_ACCESS : DENY_ACCESS;
    555   change.grfInheritance = 0;
    556   change.Trustee.pMultipleTrustee = NULL;
    557   change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
    558   change.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
    559   change.Trustee.TrusteeType = TRUSTEE_IS_USER;
    560   change.Trustee.ptstrName = L"CURRENT_USER";
    561 
    562   PACL new_dacl;
    563   if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) {
    564     LocalFree(security_descriptor);
    565     return false;
    566   }
    567 
    568   DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
    569                                   SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
    570                                   NULL, NULL, new_dacl, NULL);
    571   LocalFree(security_descriptor);
    572   LocalFree(new_dacl);
    573 
    574   return rc == ERROR_SUCCESS;
    575 #else
    576   NOTIMPLEMENTED();
    577   return false;
    578 #endif
    579 }
    580 
    581 #if defined(OS_MACOSX)
    582 // Linux implementation of FilePathWatcher doesn't catch attribute changes.
    583 // http://crbug.com/78043
    584 // Windows implementation of FilePathWatcher catches attribute changes that
    585 // don't affect the path being watched.
    586 // http://crbug.com/78045
    587 
    588 // Verify that changing attributes on a directory works.
    589 TEST_F(FilePathWatcherTest, DirAttributesChanged) {
    590   FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1"));
    591   FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2"));
    592   FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile"));
    593   // Setup a directory hierarchy.
    594   ASSERT_TRUE(file_util::CreateDirectory(test_dir1));
    595   ASSERT_TRUE(file_util::CreateDirectory(test_dir2));
    596   ASSERT_TRUE(WriteFile(test_file, "content"));
    597 
    598   FilePathWatcher watcher;
    599   scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
    600   ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get()));
    601 
    602   // We should not get notified in this case as it hasn't affected our ability
    603   // to access the file.
    604   ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false));
    605   loop_.PostDelayedTask(FROM_HERE,
    606                         new MessageLoop::QuitTask,
    607                         TestTimeouts::tiny_timeout_ms());
    608   ASSERT_FALSE(WaitForEvents());
    609   ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true));
    610 
    611   // We should get notified in this case because filepathwatcher can no
    612   // longer access the file
    613   ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false));
    614   ASSERT_TRUE(WaitForEvents());
    615   ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true));
    616 }
    617 
    618 #endif  // OS_MACOSX
    619 }  // namespace
    620 
    621 }  // namespace files
    622 }  // namespace base
    623