• 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 <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:
NotificationCollector()43   NotificationCollector()
44       : loop_(base::MessageLoopProxy::CreateForCurrentThread()) {}
45 
46   // Called from the file thread by the delegates.
OnChange(TestDelegate * delegate)47   void OnChange(TestDelegate* delegate) {
48     loop_->PostTask(FROM_HERE,
49                     NewRunnableMethod(this,
50                                       &NotificationCollector::RecordChange,
51                                       make_scoped_refptr(delegate)));
52   }
53 
Register(TestDelegate * delegate)54   void Register(TestDelegate* delegate) {
55     delegates_.insert(delegate);
56   }
57 
Reset()58   void Reset() {
59     signaled_.clear();
60   }
61 
Success()62   bool Success() {
63     return signaled_ == delegates_;
64   }
65 
66  private:
RecordChange(TestDelegate * delegate)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.
TestDelegate(NotificationCollector * collector)96   explicit TestDelegate(NotificationCollector* collector)
97       : collector_(collector) {
98     collector_->Register(this);
99   }
100 
OnFilePathChanged(const FilePath &)101   virtual void OnFilePathChanged(const FilePath&) {
102     collector_->OnChange(this);
103   }
104 
OnFilePathError(const FilePath & path)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:
SetupWatchTask(const FilePath & target,FilePathWatcher * watcher,FilePathWatcher::Delegate * delegate,bool * result,base::WaitableEvent * completion)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 
Run()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:
FilePathWatcherTest()146   FilePathWatcherTest()
147       : file_thread_("FilePathWatcherTest") {}
148 
~FilePathWatcherTest()149   virtual ~FilePathWatcherTest() {}
150 
151  protected:
SetUp()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 
TearDown()160   virtual void TearDown() {
161     loop_.RunAllPending();
162   }
163 
test_file()164   FilePath test_file() {
165     return temp_dir_.path().AppendASCII("FilePathWatcherTest");
166   }
167 
168   // Write |content| to |file|. Returns true on success.
WriteFile(const FilePath & file,const std::string & content)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 
SetupWatch(const FilePath & target,FilePathWatcher * watcher,FilePathWatcher::Delegate * delegate)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 
WaitForEvents()190   bool WaitForEvents() WARN_UNUSED_RESULT {
191     collector_->Reset();
192     loop_.Run();
193     return collector_->Success();
194   }
195 
collector()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.
TEST_F(FilePathWatcherTest,NewFile)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.
TEST_F(FilePathWatcherTest,ModifiedFile)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.
TEST_F(FilePathWatcherTest,MovedFile)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 
TEST_F(FilePathWatcherTest,DeletedFile)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:
Deleter(FilePathWatcher * watcher,MessageLoop * loop)257   Deleter(FilePathWatcher* watcher, MessageLoop* loop)
258       : watcher_(watcher),
259         loop_(loop) {
260   }
261 
OnFilePathChanged(const FilePath & path)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.
TEST_F(FilePathWatcherTest,DeleteDuringNotify)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.
TEST_F(FilePathWatcherTest,DestroyWithPendingNotification)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 
TEST_F(FilePathWatcherTest,MultipleWatchersSingleFile)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.
TEST_F(FilePathWatcherTest,NonExistentDirectory)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.
TEST_F(FilePathWatcherTest,DirectoryChain)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 
TEST_F(FilePathWatcherTest,DisappearingDirectory)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.
TEST_F(FilePathWatcherTest,DeleteAndRecreate)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 
TEST_F(FilePathWatcherTest,WatchDirectory)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 
TEST_F(FilePathWatcherTest,MoveParent)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 
TEST_F(FilePathWatcherTest,MoveChild)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
TEST_F(FilePathWatcherTest,FileAttributesChanged)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 
ChangeFilePermissions(const FilePath & path,Permission perm,bool allow)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.
TEST_F(FilePathWatcherTest,DirAttributesChanged)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