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