test_file_util_win.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 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/test/test_file_util.h"
6
7#include <windows.h>
8#include <aclapi.h>
9#include <shlwapi.h>
10
11#include <vector>
12
13#include "base/file_util.h"
14#include "base/files/file_path.h"
15#include "base/logging.h"
16#include "base/strings/string_split.h"
17#include "base/threading/platform_thread.h"
18#include "base/win/scoped_handle.h"
19
20namespace file_util {
21
22static const ptrdiff_t kOneMB = 1024 * 1024;
23
24namespace {
25
26struct PermissionInfo {
27  PSECURITY_DESCRIPTOR security_descriptor;
28  ACL dacl;
29};
30
31// Deny |permission| on the file |path|, for the current user.
32bool DenyFilePermission(const base::FilePath& path, DWORD permission) {
33  PACL old_dacl;
34  PSECURITY_DESCRIPTOR security_descriptor;
35  if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
36                           SE_FILE_OBJECT,
37                           DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl,
38                           NULL, &security_descriptor) != ERROR_SUCCESS) {
39    return false;
40  }
41
42  EXPLICIT_ACCESS change;
43  change.grfAccessPermissions = permission;
44  change.grfAccessMode = DENY_ACCESS;
45  change.grfInheritance = 0;
46  change.Trustee.pMultipleTrustee = NULL;
47  change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
48  change.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
49  change.Trustee.TrusteeType = TRUSTEE_IS_USER;
50  change.Trustee.ptstrName = L"CURRENT_USER";
51
52  PACL new_dacl;
53  if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) {
54    LocalFree(security_descriptor);
55    return false;
56  }
57
58  DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
59                                  SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
60                                  NULL, NULL, new_dacl, NULL);
61  LocalFree(security_descriptor);
62  LocalFree(new_dacl);
63
64  return rc == ERROR_SUCCESS;
65}
66
67// Gets a blob indicating the permission information for |path|.
68// |length| is the length of the blob.  Zero on failure.
69// Returns the blob pointer, or NULL on failure.
70void* GetPermissionInfo(const base::FilePath& path, size_t* length) {
71  DCHECK(length != NULL);
72  *length = 0;
73  PACL dacl = NULL;
74  PSECURITY_DESCRIPTOR security_descriptor;
75  if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
76                           SE_FILE_OBJECT,
77                           DACL_SECURITY_INFORMATION, NULL, NULL, &dacl,
78                           NULL, &security_descriptor) != ERROR_SUCCESS) {
79    return NULL;
80  }
81  DCHECK(dacl != NULL);
82
83  *length = sizeof(PSECURITY_DESCRIPTOR) + dacl->AclSize;
84  PermissionInfo* info = reinterpret_cast<PermissionInfo*>(new char[*length]);
85  info->security_descriptor = security_descriptor;
86  memcpy(&info->dacl, dacl, dacl->AclSize);
87
88  return info;
89}
90
91// Restores the permission information for |path|, given the blob retrieved
92// using |GetPermissionInfo()|.
93// |info| is the pointer to the blob.
94// |length| is the length of the blob.
95// Either |info| or |length| may be NULL/0, in which case nothing happens.
96bool RestorePermissionInfo(const base::FilePath& path,
97                           void* info, size_t length) {
98  if (!info || !length)
99    return false;
100
101  PermissionInfo* perm = reinterpret_cast<PermissionInfo*>(info);
102
103  DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
104                                  SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
105                                  NULL, NULL, &perm->dacl, NULL);
106  LocalFree(perm->security_descriptor);
107
108  char* char_array = reinterpret_cast<char*>(info);
109  delete [] char_array;
110
111  return rc == ERROR_SUCCESS;
112}
113
114}  // namespace
115
116bool DieFileDie(const base::FilePath& file, bool recurse) {
117  // It turns out that to not induce flakiness a long timeout is needed.
118  const int kIterations = 25;
119  const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(10) /
120                                   kIterations;
121
122  if (!file_util::PathExists(file))
123    return true;
124
125  // Sometimes Delete fails, so try a few more times. Divide the timeout
126  // into short chunks, so that if a try succeeds, we won't delay the test
127  // for too long.
128  for (int i = 0; i < kIterations; ++i) {
129    if (base::Delete(file, recurse))
130      return true;
131    base::PlatformThread::Sleep(kTimeout);
132  }
133  return false;
134}
135
136bool EvictFileFromSystemCache(const base::FilePath& file) {
137  // Request exclusive access to the file and overwrite it with no buffering.
138  base::win::ScopedHandle file_handle(
139      CreateFile(file.value().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
140                 OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL));
141  if (!file_handle)
142    return false;
143
144  // Get some attributes to restore later.
145  BY_HANDLE_FILE_INFORMATION bhi = {0};
146  CHECK(::GetFileInformationByHandle(file_handle, &bhi));
147
148  // Execute in chunks. It could be optimized. We want to do few of these since
149  // these operations will be slow without the cache.
150
151  // Allocate a buffer for the reads and the writes.
152  char* buffer = reinterpret_cast<char*>(VirtualAlloc(NULL,
153                                                      kOneMB,
154                                                      MEM_COMMIT | MEM_RESERVE,
155                                                      PAGE_READWRITE));
156
157  // If the file size isn't a multiple of kOneMB, we'll need special
158  // processing.
159  bool file_is_aligned = true;
160  int total_bytes = 0;
161  DWORD bytes_read, bytes_written;
162  for (;;) {
163    bytes_read = 0;
164    ::ReadFile(file_handle, buffer, kOneMB, &bytes_read, NULL);
165    if (bytes_read == 0)
166      break;
167
168    if (bytes_read < kOneMB) {
169      // Zero out the remaining part of the buffer.
170      // WriteFile will fail if we provide a buffer size that isn't a
171      // sector multiple, so we'll have to write the entire buffer with
172      // padded zeros and then use SetEndOfFile to truncate the file.
173      ZeroMemory(buffer + bytes_read, kOneMB - bytes_read);
174      file_is_aligned = false;
175    }
176
177    // Move back to the position we just read from.
178    // Note that SetFilePointer will also fail if total_bytes isn't sector
179    // aligned, but that shouldn't happen here.
180    DCHECK((total_bytes % kOneMB) == 0);
181    SetFilePointer(file_handle, total_bytes, NULL, FILE_BEGIN);
182    if (!::WriteFile(file_handle, buffer, kOneMB, &bytes_written, NULL) ||
183        bytes_written != kOneMB) {
184      BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE);
185      DCHECK(freed);
186      NOTREACHED();
187      return false;
188    }
189
190    total_bytes += bytes_read;
191
192    // If this is false, then we just processed the last portion of the file.
193    if (!file_is_aligned)
194      break;
195  }
196
197  BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE);
198  DCHECK(freed);
199
200  if (!file_is_aligned) {
201    // The size of the file isn't a multiple of 1 MB, so we'll have
202    // to open the file again, this time without the FILE_FLAG_NO_BUFFERING
203    // flag and use SetEndOfFile to mark EOF.
204    file_handle.Set(NULL);
205    file_handle.Set(CreateFile(file.value().c_str(), GENERIC_WRITE, 0, NULL,
206                               OPEN_EXISTING, 0, NULL));
207    CHECK_NE(SetFilePointer(file_handle, total_bytes, NULL, FILE_BEGIN),
208             INVALID_SET_FILE_POINTER);
209    CHECK(::SetEndOfFile(file_handle));
210  }
211
212  // Restore the file attributes.
213  CHECK(::SetFileTime(file_handle, &bhi.ftCreationTime, &bhi.ftLastAccessTime,
214                      &bhi.ftLastWriteTime));
215
216  return true;
217}
218
219// Checks if the volume supports Alternate Data Streams. This is required for
220// the Zone Identifier implementation.
221bool VolumeSupportsADS(const base::FilePath& path) {
222  wchar_t drive[MAX_PATH] = {0};
223  wcscpy_s(drive, MAX_PATH, path.value().c_str());
224
225  if (!PathStripToRootW(drive))
226    return false;
227
228  DWORD fs_flags = 0;
229  if (!GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL, 0))
230    return false;
231
232  if (fs_flags & FILE_NAMED_STREAMS)
233    return true;
234
235  return false;
236}
237
238// Return whether the ZoneIdentifier is correctly set to "Internet" (3)
239// Only returns a valid result when called from same process as the
240// one that (was supposed to have) set the zone identifier.
241bool HasInternetZoneIdentifier(const base::FilePath& full_path) {
242  base::FilePath zone_path(full_path.value() + L":Zone.Identifier");
243  std::string zone_path_contents;
244  if (!file_util::ReadFileToString(zone_path, &zone_path_contents))
245    return false;
246
247  std::vector<std::string> lines;
248  // This call also trims whitespaces, including carriage-returns (\r).
249  base::SplitString(zone_path_contents, '\n', &lines);
250
251  switch (lines.size()) {
252    case 3:
253      // optional empty line at end of file:
254      if (lines[2] != "")
255        return false;
256      // fall through:
257    case 2:
258      return lines[0] == "[ZoneTransfer]" && lines[1] == "ZoneId=3";
259    default:
260      return false;
261  }
262}
263
264std::wstring FilePathAsWString(const base::FilePath& path) {
265  return path.value();
266}
267base::FilePath WStringAsFilePath(const std::wstring& path) {
268  return base::FilePath(path);
269}
270
271bool MakeFileUnreadable(const base::FilePath& path) {
272  return DenyFilePermission(path, GENERIC_READ);
273}
274
275bool MakeFileUnwritable(const base::FilePath& path) {
276  return DenyFilePermission(path, GENERIC_WRITE);
277}
278
279PermissionRestorer::PermissionRestorer(const base::FilePath& path)
280    : path_(path), info_(NULL), length_(0) {
281  info_ = GetPermissionInfo(path_, &length_);
282  DCHECK(info_ != NULL);
283  DCHECK_NE(0u, length_);
284}
285
286PermissionRestorer::~PermissionRestorer() {
287  if (!RestorePermissionInfo(path_, info_, length_))
288    NOTREACHED();
289}
290
291}  // namespace file_util
292