1 // Copyright 2012 The Chromium Authors
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/test/test_file_util.h"
6
7 #include <aclapi.h>
8 #include <stddef.h>
9 #include <wchar.h>
10 #include <windows.h>
11
12 #include <memory>
13
14 #include "base/check_op.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/memory/ptr_util.h"
18 #include "base/notreached.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/threading/platform_thread.h"
22 #include "base/win/scoped_handle.h"
23 #include "base/win/shlwapi.h"
24
25 namespace base {
26
27 namespace {
28
29 struct PermissionInfo {
30 PSECURITY_DESCRIPTOR security_descriptor;
31 ACL dacl;
32 };
33
34 // Gets a blob indicating the permission information for |path|.
35 // |length| is the length of the blob. Zero on failure.
36 // Returns the blob pointer, or NULL on failure.
GetPermissionInfo(const FilePath & path,size_t * length)37 void* GetPermissionInfo(const FilePath& path, size_t* length) {
38 DCHECK(length);
39 *length = 0;
40 PACL dacl = nullptr;
41 PSECURITY_DESCRIPTOR security_descriptor;
42 if (GetNamedSecurityInfo(path.value().c_str(), SE_FILE_OBJECT,
43 DACL_SECURITY_INFORMATION, nullptr, nullptr, &dacl,
44 nullptr, &security_descriptor) != ERROR_SUCCESS) {
45 return nullptr;
46 }
47 DCHECK(dacl);
48
49 *length = sizeof(PSECURITY_DESCRIPTOR) + dacl->AclSize;
50 PermissionInfo* info = reinterpret_cast<PermissionInfo*>(new char[*length]);
51 info->security_descriptor = security_descriptor;
52 memcpy(&info->dacl, dacl, dacl->AclSize);
53
54 return info;
55 }
56
57 // Restores the permission information for |path|, given the blob retrieved
58 // using |GetPermissionInfo()|.
59 // |info| is the pointer to the blob.
60 // |length| is the length of the blob.
61 // Either |info| or |length| may be NULL/0, in which case nothing happens.
RestorePermissionInfo(const FilePath & path,void * info,size_t length)62 bool RestorePermissionInfo(const FilePath& path, void* info, size_t length) {
63 if (!info || !length)
64 return false;
65
66 PermissionInfo* perm = reinterpret_cast<PermissionInfo*>(info);
67
68 DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
69 SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
70 nullptr, nullptr, &perm->dacl, nullptr);
71 LocalFree(perm->security_descriptor);
72
73 char* char_array = reinterpret_cast<char*>(info);
74 delete [] char_array;
75
76 return rc == ERROR_SUCCESS;
77 }
78
ToCStr(const std::basic_string<wchar_t> & str)79 std::unique_ptr<wchar_t[]> ToCStr(const std::basic_string<wchar_t>& str) {
80 size_t size = str.size() + 1;
81 std::unique_ptr<wchar_t[]> ptr = std::make_unique<wchar_t[]>(size);
82 wcsncpy(ptr.get(), str.c_str(), size);
83 return ptr;
84 }
85
86 } // namespace
87
DieFileDie(const FilePath & file,bool recurse)88 bool DieFileDie(const FilePath& file, bool recurse) {
89 // It turns out that to not induce flakiness a long timeout is needed.
90 const int kIterations = 25;
91 const TimeDelta kTimeout = Seconds(10) / kIterations;
92
93 if (!PathExists(file))
94 return true;
95
96 // Sometimes Delete fails, so try a few more times. Divide the timeout
97 // into short chunks, so that if a try succeeds, we won't delay the test
98 // for too long.
99 for (int i = 0; i < kIterations; ++i) {
100 bool success;
101 if (recurse)
102 success = DeletePathRecursively(file);
103 else
104 success = DeleteFile(file);
105 if (success)
106 return true;
107 PlatformThread::Sleep(kTimeout);
108 }
109 return false;
110 }
111
SyncPageCacheToDisk()112 void SyncPageCacheToDisk() {
113 // Approximating this with noop. The proper implementation would require
114 // administrator privilege:
115 // https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-flushfilebuffers
116 }
117
EvictFileFromSystemCache(const FilePath & file)118 bool EvictFileFromSystemCache(const FilePath& file) {
119 FilePath::StringType file_value = file.value();
120 if (file_value.length() >= MAX_PATH && file.IsAbsolute()) {
121 file_value.insert(0, L"\\\\?\\");
122 }
123 win::ScopedHandle file_handle(
124 CreateFile(file_value.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr,
125 OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, nullptr));
126 if (!file_handle.is_valid())
127 return false;
128
129 // Re-write the file time information to trigger cache eviction for the file.
130 // This function previously overwrote the entire file without buffering, but
131 // local experimentation validates this simplified and *much* faster approach:
132 // [1] Sysinternals RamMap no longer lists these files as cached afterwards.
133 // [2] Telemetry performance test startup.cold.blank_page reports sane values.
134 BY_HANDLE_FILE_INFORMATION bhi = {0};
135 CHECK(::GetFileInformationByHandle(file_handle.get(), &bhi));
136 CHECK(::SetFileTime(file_handle.get(), &bhi.ftCreationTime,
137 &bhi.ftLastAccessTime, &bhi.ftLastWriteTime));
138 return true;
139 }
140
141 // Deny |permission| on the file |path|, for the current user.
DenyFilePermission(const FilePath & path,DWORD permission)142 bool DenyFilePermission(const FilePath& path, DWORD permission) {
143 PACL old_dacl;
144 PSECURITY_DESCRIPTOR security_descriptor;
145
146 std::unique_ptr<TCHAR[]> path_ptr = ToCStr(path.value().c_str());
147 if (GetNamedSecurityInfo(path_ptr.get(), SE_FILE_OBJECT,
148 DACL_SECURITY_INFORMATION, nullptr, nullptr,
149 &old_dacl, nullptr,
150 &security_descriptor) != ERROR_SUCCESS) {
151 return false;
152 }
153
154 std::unique_ptr<TCHAR[]> current_user = ToCStr(std::wstring(L"CURRENT_USER"));
155 EXPLICIT_ACCESS new_access = {
156 permission,
157 DENY_ACCESS,
158 0,
159 {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME, TRUSTEE_IS_USER,
160 current_user.get()}};
161
162 PACL new_dacl;
163 if (SetEntriesInAcl(1, &new_access, old_dacl, &new_dacl) != ERROR_SUCCESS) {
164 LocalFree(security_descriptor);
165 return false;
166 }
167
168 DWORD rc = SetNamedSecurityInfo(path_ptr.get(), SE_FILE_OBJECT,
169 DACL_SECURITY_INFORMATION, nullptr, nullptr,
170 new_dacl, nullptr);
171 LocalFree(security_descriptor);
172 LocalFree(new_dacl);
173
174 return rc == ERROR_SUCCESS;
175 }
176
MakeFileUnreadable(const FilePath & path)177 bool MakeFileUnreadable(const FilePath& path) {
178 return DenyFilePermission(path, GENERIC_READ);
179 }
180
MakeFileUnwritable(const FilePath & path)181 bool MakeFileUnwritable(const FilePath& path) {
182 return DenyFilePermission(path, GENERIC_WRITE);
183 }
184
FilePermissionRestorer(const FilePath & path)185 FilePermissionRestorer::FilePermissionRestorer(const FilePath& path)
186 : path_(path), info_(nullptr), length_(0) {
187 info_ = GetPermissionInfo(path_, &length_);
188 DCHECK(info_);
189 DCHECK_NE(0u, length_);
190 }
191
~FilePermissionRestorer()192 FilePermissionRestorer::~FilePermissionRestorer() {
193 if (!RestorePermissionInfo(path_, info_, length_))
194 NOTREACHED();
195 }
196
197 } // namespace base
198