test_file_util_win.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 <aclapi.h> 8#include <shlwapi.h> 9#include <windows.h> 10 11#include <vector> 12 13#include "base/file_path.h" 14#include "base/file_util.h" 15#include "base/logging.h" 16#include "base/string_split.h" 17#include "base/win/scoped_handle.h" 18#include "base/threading/platform_thread.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 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 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 FilePath& path, void* info, size_t length) { 97 if (!info || !length) 98 return false; 99 100 PermissionInfo* perm = reinterpret_cast<PermissionInfo*>(info); 101 102 DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), 103 SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, 104 NULL, NULL, &perm->dacl, NULL); 105 LocalFree(perm->security_descriptor); 106 107 char* char_array = reinterpret_cast<char*>(info); 108 delete [] char_array; 109 110 return rc == ERROR_SUCCESS; 111} 112 113} // namespace 114 115bool DieFileDie(const FilePath& file, bool recurse) { 116 // It turns out that to not induce flakiness a long timeout is needed. 117 const int kIterations = 25; 118 const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(10) / 119 kIterations; 120 121 if (!file_util::PathExists(file)) 122 return true; 123 124 // Sometimes Delete fails, so try a few more times. Divide the timeout 125 // into short chunks, so that if a try succeeds, we won't delay the test 126 // for too long. 127 for (int i = 0; i < kIterations; ++i) { 128 if (file_util::Delete(file, recurse)) 129 return true; 130 base::PlatformThread::Sleep(kTimeout); 131 } 132 return false; 133} 134 135bool EvictFileFromSystemCache(const FilePath& file) { 136 // Request exclusive access to the file and overwrite it with no buffering. 137 base::win::ScopedHandle file_handle( 138 CreateFile(file.value().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, 139 OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL)); 140 if (!file_handle) 141 return false; 142 143 // Get some attributes to restore later. 144 BY_HANDLE_FILE_INFORMATION bhi = {0}; 145 CHECK(::GetFileInformationByHandle(file_handle, &bhi)); 146 147 // Execute in chunks. It could be optimized. We want to do few of these since 148 // these operations will be slow without the cache. 149 150 // Allocate a buffer for the reads and the writes. 151 char* buffer = reinterpret_cast<char*>(VirtualAlloc(NULL, 152 kOneMB, 153 MEM_COMMIT | MEM_RESERVE, 154 PAGE_READWRITE)); 155 156 // If the file size isn't a multiple of kOneMB, we'll need special 157 // processing. 158 bool file_is_aligned = true; 159 int total_bytes = 0; 160 DWORD bytes_read, bytes_written; 161 for (;;) { 162 bytes_read = 0; 163 ::ReadFile(file_handle, buffer, kOneMB, &bytes_read, NULL); 164 if (bytes_read == 0) 165 break; 166 167 if (bytes_read < kOneMB) { 168 // Zero out the remaining part of the buffer. 169 // WriteFile will fail if we provide a buffer size that isn't a 170 // sector multiple, so we'll have to write the entire buffer with 171 // padded zeros and then use SetEndOfFile to truncate the file. 172 ZeroMemory(buffer + bytes_read, kOneMB - bytes_read); 173 file_is_aligned = false; 174 } 175 176 // Move back to the position we just read from. 177 // Note that SetFilePointer will also fail if total_bytes isn't sector 178 // aligned, but that shouldn't happen here. 179 DCHECK((total_bytes % kOneMB) == 0); 180 SetFilePointer(file_handle, total_bytes, NULL, FILE_BEGIN); 181 if (!::WriteFile(file_handle, buffer, kOneMB, &bytes_written, NULL) || 182 bytes_written != kOneMB) { 183 BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE); 184 DCHECK(freed); 185 NOTREACHED(); 186 return false; 187 } 188 189 total_bytes += bytes_read; 190 191 // If this is false, then we just processed the last portion of the file. 192 if (!file_is_aligned) 193 break; 194 } 195 196 BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE); 197 DCHECK(freed); 198 199 if (!file_is_aligned) { 200 // The size of the file isn't a multiple of 1 MB, so we'll have 201 // to open the file again, this time without the FILE_FLAG_NO_BUFFERING 202 // flag and use SetEndOfFile to mark EOF. 203 file_handle.Set(NULL); 204 file_handle.Set(CreateFile(file.value().c_str(), GENERIC_WRITE, 0, NULL, 205 OPEN_EXISTING, 0, NULL)); 206 CHECK_NE(SetFilePointer(file_handle, total_bytes, NULL, FILE_BEGIN), 207 INVALID_SET_FILE_POINTER); 208 CHECK(::SetEndOfFile(file_handle)); 209 } 210 211 // Restore the file attributes. 212 CHECK(::SetFileTime(file_handle, &bhi.ftCreationTime, &bhi.ftLastAccessTime, 213 &bhi.ftLastWriteTime)); 214 215 return true; 216} 217 218// Like CopyFileNoCache but recursively copies all files and subdirectories 219// in the given input directory to the output directory. 220bool CopyRecursiveDirNoCache(const FilePath& source_dir, 221 const FilePath& dest_dir) { 222 // Try to create the directory if it doesn't already exist. 223 if (!CreateDirectory(dest_dir)) { 224 if (GetLastError() != ERROR_ALREADY_EXISTS) 225 return false; 226 } 227 228 std::vector<std::wstring> files_copied; 229 230 FilePath src(source_dir.AppendASCII("*")); 231 232 WIN32_FIND_DATA fd; 233 HANDLE fh = FindFirstFile(src.value().c_str(), &fd); 234 if (fh == INVALID_HANDLE_VALUE) 235 return false; 236 237 do { 238 std::wstring cur_file(fd.cFileName); 239 if (cur_file == L"." || cur_file == L"..") 240 continue; // Skip these special entries. 241 242 FilePath cur_source_path = source_dir.Append(cur_file); 243 FilePath cur_dest_path = dest_dir.Append(cur_file); 244 245 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 246 // Recursively copy a subdirectory. We stripped "." and ".." already. 247 if (!CopyRecursiveDirNoCache(cur_source_path, cur_dest_path)) { 248 FindClose(fh); 249 return false; 250 } 251 } else { 252 // Copy the file. 253 if (!::CopyFile(cur_source_path.value().c_str(), 254 cur_dest_path.value().c_str(), false)) { 255 FindClose(fh); 256 return false; 257 } 258 259 // We don't check for errors from this function, often, we are copying 260 // files that are in the repository, and they will have read-only set. 261 // This will prevent us from evicting from the cache, but these don't 262 // matter anyway. 263 EvictFileFromSystemCache(cur_dest_path); 264 } 265 } while (FindNextFile(fh, &fd)); 266 267 FindClose(fh); 268 return true; 269} 270 271// Checks if the volume supports Alternate Data Streams. This is required for 272// the Zone Identifier implementation. 273bool VolumeSupportsADS(const FilePath& path) { 274 wchar_t drive[MAX_PATH] = {0}; 275 wcscpy_s(drive, MAX_PATH, path.value().c_str()); 276 277 if (!PathStripToRootW(drive)) 278 return false; 279 280 DWORD fs_flags = 0; 281 if (!GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL, 0)) 282 return false; 283 284 if (fs_flags & FILE_NAMED_STREAMS) 285 return true; 286 287 return false; 288} 289 290// Return whether the ZoneIdentifier is correctly set to "Internet" (3) 291// Only returns a valid result when called from same process as the 292// one that (was supposed to have) set the zone identifier. 293bool HasInternetZoneIdentifier(const FilePath& full_path) { 294 FilePath zone_path(full_path.value() + L":Zone.Identifier"); 295 std::string zone_path_contents; 296 if (!file_util::ReadFileToString(zone_path, &zone_path_contents)) 297 return false; 298 299 std::vector<std::string> lines; 300 // This call also trims whitespaces, including carriage-returns (\r). 301 base::SplitString(zone_path_contents, '\n', &lines); 302 303 switch (lines.size()) { 304 case 3: 305 // optional empty line at end of file: 306 if (lines[2] != "") 307 return false; 308 // fall through: 309 case 2: 310 return lines[0] == "[ZoneTransfer]" && lines[1] == "ZoneId=3"; 311 default: 312 return false; 313 } 314} 315 316std::wstring FilePathAsWString(const FilePath& path) { 317 return path.value(); 318} 319FilePath WStringAsFilePath(const std::wstring& path) { 320 return FilePath(path); 321} 322 323bool MakeFileUnreadable(const FilePath& path) { 324 return DenyFilePermission(path, GENERIC_READ); 325} 326 327bool MakeFileUnwritable(const FilePath& path) { 328 return DenyFilePermission(path, GENERIC_WRITE); 329} 330 331PermissionRestorer::PermissionRestorer(const FilePath& path) 332 : path_(path), info_(NULL), length_(0) { 333 info_ = GetPermissionInfo(path_, &length_); 334 DCHECK(info_ != NULL); 335 DCHECK_NE(0u, length_); 336} 337 338PermissionRestorer::~PermissionRestorer() { 339 if (!RestorePermissionInfo(path_, info_, length_)) 340 NOTREACHED(); 341} 342 343} // namespace file_util 344