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