security/sandbox/chromium/base/file_util_win.cc

Wed, 31 Dec 2014 07:16:47 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:16:47 +0100
branch
TOR_BUG_9701
changeset 3
141e0f1194b1
permissions
-rw-r--r--

Revert simplistic fix pending revisit of Mozilla integration attempt.

michael@0 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
michael@0 2 // Use of this source code is governed by a BSD-style license that can be
michael@0 3 // found in the LICENSE file.
michael@0 4
michael@0 5 #include "base/file_util.h"
michael@0 6
michael@0 7 #include <windows.h>
michael@0 8 #include <psapi.h>
michael@0 9 #include <shellapi.h>
michael@0 10 #include <shlobj.h>
michael@0 11 #include <time.h>
michael@0 12
michael@0 13 #include <algorithm>
michael@0 14 #include <limits>
michael@0 15 #include <string>
michael@0 16
michael@0 17 #include "base/files/file_path.h"
michael@0 18 #include "base/logging.h"
michael@0 19 #include "base/metrics/histogram.h"
michael@0 20 #include "base/process/process_handle.h"
michael@0 21 #include "base/rand_util.h"
michael@0 22 #include "base/strings/string_number_conversions.h"
michael@0 23 #include "base/strings/string_util.h"
michael@0 24 #include "base/strings/utf_string_conversions.h"
michael@0 25 #include "base/threading/thread_restrictions.h"
michael@0 26 #include "base/time/time.h"
michael@0 27 #include "base/win/scoped_handle.h"
michael@0 28 #include "base/win/windows_version.h"
michael@0 29
michael@0 30 namespace base {
michael@0 31
michael@0 32 namespace {
michael@0 33
michael@0 34 const DWORD kFileShareAll =
michael@0 35 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
michael@0 36
michael@0 37 bool ShellCopy(const FilePath& from_path,
michael@0 38 const FilePath& to_path,
michael@0 39 bool recursive) {
michael@0 40 // WinXP SHFileOperation doesn't like trailing separators.
michael@0 41 FilePath stripped_from = from_path.StripTrailingSeparators();
michael@0 42 FilePath stripped_to = to_path.StripTrailingSeparators();
michael@0 43
michael@0 44 ThreadRestrictions::AssertIOAllowed();
michael@0 45
michael@0 46 // NOTE: I suspect we could support longer paths, but that would involve
michael@0 47 // analyzing all our usage of files.
michael@0 48 if (stripped_from.value().length() >= MAX_PATH ||
michael@0 49 stripped_to.value().length() >= MAX_PATH) {
michael@0 50 return false;
michael@0 51 }
michael@0 52
michael@0 53 // SHFILEOPSTRUCT wants the path to be terminated with two NULLs,
michael@0 54 // so we have to use wcscpy because wcscpy_s writes non-NULLs
michael@0 55 // into the rest of the buffer.
michael@0 56 wchar_t double_terminated_path_from[MAX_PATH + 1] = {0};
michael@0 57 wchar_t double_terminated_path_to[MAX_PATH + 1] = {0};
michael@0 58 #pragma warning(suppress:4996) // don't complain about wcscpy deprecation
michael@0 59 wcscpy(double_terminated_path_from, stripped_from.value().c_str());
michael@0 60 #pragma warning(suppress:4996) // don't complain about wcscpy deprecation
michael@0 61 wcscpy(double_terminated_path_to, stripped_to.value().c_str());
michael@0 62
michael@0 63 SHFILEOPSTRUCT file_operation = {0};
michael@0 64 file_operation.wFunc = FO_COPY;
michael@0 65 file_operation.pFrom = double_terminated_path_from;
michael@0 66 file_operation.pTo = double_terminated_path_to;
michael@0 67 file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION |
michael@0 68 FOF_NOCONFIRMMKDIR;
michael@0 69 if (!recursive)
michael@0 70 file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
michael@0 71
michael@0 72 return (SHFileOperation(&file_operation) == 0);
michael@0 73 }
michael@0 74
michael@0 75 } // namespace
michael@0 76
michael@0 77 FilePath MakeAbsoluteFilePath(const FilePath& input) {
michael@0 78 ThreadRestrictions::AssertIOAllowed();
michael@0 79 wchar_t file_path[MAX_PATH];
michael@0 80 if (!_wfullpath(file_path, input.value().c_str(), MAX_PATH))
michael@0 81 return FilePath();
michael@0 82 return FilePath(file_path);
michael@0 83 }
michael@0 84
michael@0 85 bool DeleteFile(const FilePath& path, bool recursive) {
michael@0 86 ThreadRestrictions::AssertIOAllowed();
michael@0 87
michael@0 88 if (path.value().length() >= MAX_PATH)
michael@0 89 return false;
michael@0 90
michael@0 91 if (!recursive) {
michael@0 92 // If not recursing, then first check to see if |path| is a directory.
michael@0 93 // If it is, then remove it with RemoveDirectory.
michael@0 94 PlatformFileInfo file_info;
michael@0 95 if (file_util::GetFileInfo(path, &file_info) && file_info.is_directory)
michael@0 96 return RemoveDirectory(path.value().c_str()) != 0;
michael@0 97
michael@0 98 // Otherwise, it's a file, wildcard or non-existant. Try DeleteFile first
michael@0 99 // because it should be faster. If DeleteFile fails, then we fall through
michael@0 100 // to SHFileOperation, which will do the right thing.
michael@0 101 if (::DeleteFile(path.value().c_str()) != 0)
michael@0 102 return true;
michael@0 103 }
michael@0 104
michael@0 105 // SHFILEOPSTRUCT wants the path to be terminated with two NULLs,
michael@0 106 // so we have to use wcscpy because wcscpy_s writes non-NULLs
michael@0 107 // into the rest of the buffer.
michael@0 108 wchar_t double_terminated_path[MAX_PATH + 1] = {0};
michael@0 109 #pragma warning(suppress:4996) // don't complain about wcscpy deprecation
michael@0 110 if (g_bug108724_debug)
michael@0 111 LOG(WARNING) << "copying ";
michael@0 112 wcscpy(double_terminated_path, path.value().c_str());
michael@0 113
michael@0 114 SHFILEOPSTRUCT file_operation = {0};
michael@0 115 file_operation.wFunc = FO_DELETE;
michael@0 116 file_operation.pFrom = double_terminated_path;
michael@0 117 file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION;
michael@0 118 if (!recursive)
michael@0 119 file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
michael@0 120 if (g_bug108724_debug)
michael@0 121 LOG(WARNING) << "Performing shell operation";
michael@0 122 int err = SHFileOperation(&file_operation);
michael@0 123 if (g_bug108724_debug)
michael@0 124 LOG(WARNING) << "Done: " << err;
michael@0 125
michael@0 126 // Since we're passing flags to the operation telling it to be silent,
michael@0 127 // it's possible for the operation to be aborted/cancelled without err
michael@0 128 // being set (although MSDN doesn't give any scenarios for how this can
michael@0 129 // happen). See MSDN for SHFileOperation and SHFILEOPTSTRUCT.
michael@0 130 if (file_operation.fAnyOperationsAborted)
michael@0 131 return false;
michael@0 132
michael@0 133 // Some versions of Windows return ERROR_FILE_NOT_FOUND (0x2) when deleting
michael@0 134 // an empty directory and some return 0x402 when they should be returning
michael@0 135 // ERROR_FILE_NOT_FOUND. MSDN says Vista and up won't return 0x402.
michael@0 136 return (err == 0 || err == ERROR_FILE_NOT_FOUND || err == 0x402);
michael@0 137 }
michael@0 138
michael@0 139 bool DeleteFileAfterReboot(const FilePath& path) {
michael@0 140 ThreadRestrictions::AssertIOAllowed();
michael@0 141
michael@0 142 if (path.value().length() >= MAX_PATH)
michael@0 143 return false;
michael@0 144
michael@0 145 return MoveFileEx(path.value().c_str(), NULL,
michael@0 146 MOVEFILE_DELAY_UNTIL_REBOOT |
michael@0 147 MOVEFILE_REPLACE_EXISTING) != FALSE;
michael@0 148 }
michael@0 149
michael@0 150 bool ReplaceFile(const FilePath& from_path,
michael@0 151 const FilePath& to_path,
michael@0 152 PlatformFileError* error) {
michael@0 153 ThreadRestrictions::AssertIOAllowed();
michael@0 154 // Try a simple move first. It will only succeed when |to_path| doesn't
michael@0 155 // already exist.
michael@0 156 if (::MoveFile(from_path.value().c_str(), to_path.value().c_str()))
michael@0 157 return true;
michael@0 158 // Try the full-blown replace if the move fails, as ReplaceFile will only
michael@0 159 // succeed when |to_path| does exist. When writing to a network share, we may
michael@0 160 // not be able to change the ACLs. Ignore ACL errors then
michael@0 161 // (REPLACEFILE_IGNORE_MERGE_ERRORS).
michael@0 162 if (::ReplaceFile(to_path.value().c_str(), from_path.value().c_str(), NULL,
michael@0 163 REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) {
michael@0 164 return true;
michael@0 165 }
michael@0 166 if (error)
michael@0 167 *error = LastErrorToPlatformFileError(GetLastError());
michael@0 168 return false;
michael@0 169 }
michael@0 170
michael@0 171 bool CopyDirectory(const FilePath& from_path, const FilePath& to_path,
michael@0 172 bool recursive) {
michael@0 173 ThreadRestrictions::AssertIOAllowed();
michael@0 174
michael@0 175 if (recursive)
michael@0 176 return ShellCopy(from_path, to_path, true);
michael@0 177
michael@0 178 // The following code assumes that from path is a directory.
michael@0 179 DCHECK(DirectoryExists(from_path));
michael@0 180
michael@0 181 // Instead of creating a new directory, we copy the old one to include the
michael@0 182 // security information of the folder as part of the copy.
michael@0 183 if (!PathExists(to_path)) {
michael@0 184 // Except that Vista fails to do that, and instead do a recursive copy if
michael@0 185 // the target directory doesn't exist.
michael@0 186 if (base::win::GetVersion() >= base::win::VERSION_VISTA)
michael@0 187 file_util::CreateDirectory(to_path);
michael@0 188 else
michael@0 189 ShellCopy(from_path, to_path, false);
michael@0 190 }
michael@0 191
michael@0 192 FilePath directory = from_path.Append(L"*.*");
michael@0 193 return ShellCopy(directory, to_path, false);
michael@0 194 }
michael@0 195
michael@0 196 bool PathExists(const FilePath& path) {
michael@0 197 ThreadRestrictions::AssertIOAllowed();
michael@0 198 return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES);
michael@0 199 }
michael@0 200
michael@0 201 bool PathIsWritable(const FilePath& path) {
michael@0 202 ThreadRestrictions::AssertIOAllowed();
michael@0 203 HANDLE dir =
michael@0 204 CreateFile(path.value().c_str(), FILE_ADD_FILE, kFileShareAll,
michael@0 205 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
michael@0 206
michael@0 207 if (dir == INVALID_HANDLE_VALUE)
michael@0 208 return false;
michael@0 209
michael@0 210 CloseHandle(dir);
michael@0 211 return true;
michael@0 212 }
michael@0 213
michael@0 214 bool DirectoryExists(const FilePath& path) {
michael@0 215 ThreadRestrictions::AssertIOAllowed();
michael@0 216 DWORD fileattr = GetFileAttributes(path.value().c_str());
michael@0 217 if (fileattr != INVALID_FILE_ATTRIBUTES)
michael@0 218 return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0;
michael@0 219 return false;
michael@0 220 }
michael@0 221
michael@0 222 } // namespace base
michael@0 223
michael@0 224 // -----------------------------------------------------------------------------
michael@0 225
michael@0 226 namespace file_util {
michael@0 227
michael@0 228 using base::DirectoryExists;
michael@0 229 using base::FilePath;
michael@0 230 using base::kFileShareAll;
michael@0 231
michael@0 232 bool GetTempDir(FilePath* path) {
michael@0 233 base::ThreadRestrictions::AssertIOAllowed();
michael@0 234
michael@0 235 wchar_t temp_path[MAX_PATH + 1];
michael@0 236 DWORD path_len = ::GetTempPath(MAX_PATH, temp_path);
michael@0 237 if (path_len >= MAX_PATH || path_len <= 0)
michael@0 238 return false;
michael@0 239 // TODO(evanm): the old behavior of this function was to always strip the
michael@0 240 // trailing slash. We duplicate this here, but it shouldn't be necessary
michael@0 241 // when everyone is using the appropriate FilePath APIs.
michael@0 242 *path = FilePath(temp_path).StripTrailingSeparators();
michael@0 243 return true;
michael@0 244 }
michael@0 245
michael@0 246 bool GetShmemTempDir(FilePath* path, bool executable) {
michael@0 247 return GetTempDir(path);
michael@0 248 }
michael@0 249
michael@0 250 bool CreateTemporaryFile(FilePath* path) {
michael@0 251 base::ThreadRestrictions::AssertIOAllowed();
michael@0 252
michael@0 253 FilePath temp_file;
michael@0 254
michael@0 255 if (!GetTempDir(path))
michael@0 256 return false;
michael@0 257
michael@0 258 if (CreateTemporaryFileInDir(*path, &temp_file)) {
michael@0 259 *path = temp_file;
michael@0 260 return true;
michael@0 261 }
michael@0 262
michael@0 263 return false;
michael@0 264 }
michael@0 265
michael@0 266 FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) {
michael@0 267 base::ThreadRestrictions::AssertIOAllowed();
michael@0 268 return CreateAndOpenTemporaryFile(path);
michael@0 269 }
michael@0 270
michael@0 271 // On POSIX we have semantics to create and open a temporary file
michael@0 272 // atomically.
michael@0 273 // TODO(jrg): is there equivalent call to use on Windows instead of
michael@0 274 // going 2-step?
michael@0 275 FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) {
michael@0 276 base::ThreadRestrictions::AssertIOAllowed();
michael@0 277 if (!CreateTemporaryFileInDir(dir, path)) {
michael@0 278 return NULL;
michael@0 279 }
michael@0 280 // Open file in binary mode, to avoid problems with fwrite. On Windows
michael@0 281 // it replaces \n's with \r\n's, which may surprise you.
michael@0 282 // Reference: http://msdn.microsoft.com/en-us/library/h9t88zwz(VS.71).aspx
michael@0 283 return OpenFile(*path, "wb+");
michael@0 284 }
michael@0 285
michael@0 286 bool CreateTemporaryFileInDir(const FilePath& dir,
michael@0 287 FilePath* temp_file) {
michael@0 288 base::ThreadRestrictions::AssertIOAllowed();
michael@0 289
michael@0 290 wchar_t temp_name[MAX_PATH + 1];
michael@0 291
michael@0 292 if (!GetTempFileName(dir.value().c_str(), L"", 0, temp_name)) {
michael@0 293 DPLOG(WARNING) << "Failed to get temporary file name in " << dir.value();
michael@0 294 return false;
michael@0 295 }
michael@0 296
michael@0 297 wchar_t long_temp_name[MAX_PATH + 1];
michael@0 298 DWORD long_name_len = GetLongPathName(temp_name, long_temp_name, MAX_PATH);
michael@0 299 if (long_name_len > MAX_PATH || long_name_len == 0) {
michael@0 300 // GetLongPathName() failed, but we still have a temporary file.
michael@0 301 *temp_file = FilePath(temp_name);
michael@0 302 return true;
michael@0 303 }
michael@0 304
michael@0 305 FilePath::StringType long_temp_name_str;
michael@0 306 long_temp_name_str.assign(long_temp_name, long_name_len);
michael@0 307 *temp_file = FilePath(long_temp_name_str);
michael@0 308 return true;
michael@0 309 }
michael@0 310
michael@0 311 bool CreateTemporaryDirInDir(const FilePath& base_dir,
michael@0 312 const FilePath::StringType& prefix,
michael@0 313 FilePath* new_dir) {
michael@0 314 base::ThreadRestrictions::AssertIOAllowed();
michael@0 315
michael@0 316 FilePath path_to_create;
michael@0 317
michael@0 318 for (int count = 0; count < 50; ++count) {
michael@0 319 // Try create a new temporary directory with random generated name. If
michael@0 320 // the one exists, keep trying another path name until we reach some limit.
michael@0 321 string16 new_dir_name;
michael@0 322 new_dir_name.assign(prefix);
michael@0 323 new_dir_name.append(base::IntToString16(::base::GetCurrentProcId()));
michael@0 324 new_dir_name.push_back('_');
michael@0 325 new_dir_name.append(base::IntToString16(base::RandInt(0, kint16max)));
michael@0 326
michael@0 327 path_to_create = base_dir.Append(new_dir_name);
michael@0 328 if (::CreateDirectory(path_to_create.value().c_str(), NULL)) {
michael@0 329 *new_dir = path_to_create;
michael@0 330 return true;
michael@0 331 }
michael@0 332 }
michael@0 333
michael@0 334 return false;
michael@0 335 }
michael@0 336
michael@0 337 bool CreateNewTempDirectory(const FilePath::StringType& prefix,
michael@0 338 FilePath* new_temp_path) {
michael@0 339 base::ThreadRestrictions::AssertIOAllowed();
michael@0 340
michael@0 341 FilePath system_temp_dir;
michael@0 342 if (!GetTempDir(&system_temp_dir))
michael@0 343 return false;
michael@0 344
michael@0 345 return CreateTemporaryDirInDir(system_temp_dir, prefix, new_temp_path);
michael@0 346 }
michael@0 347
michael@0 348 bool CreateDirectoryAndGetError(const FilePath& full_path,
michael@0 349 base::PlatformFileError* error) {
michael@0 350 base::ThreadRestrictions::AssertIOAllowed();
michael@0 351
michael@0 352 // If the path exists, we've succeeded if it's a directory, failed otherwise.
michael@0 353 const wchar_t* full_path_str = full_path.value().c_str();
michael@0 354 DWORD fileattr = ::GetFileAttributes(full_path_str);
michael@0 355 if (fileattr != INVALID_FILE_ATTRIBUTES) {
michael@0 356 if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
michael@0 357 DVLOG(1) << "CreateDirectory(" << full_path_str << "), "
michael@0 358 << "directory already exists.";
michael@0 359 return true;
michael@0 360 }
michael@0 361 DLOG(WARNING) << "CreateDirectory(" << full_path_str << "), "
michael@0 362 << "conflicts with existing file.";
michael@0 363 if (error) {
michael@0 364 *error = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
michael@0 365 }
michael@0 366 return false;
michael@0 367 }
michael@0 368
michael@0 369 // Invariant: Path does not exist as file or directory.
michael@0 370
michael@0 371 // Attempt to create the parent recursively. This will immediately return
michael@0 372 // true if it already exists, otherwise will create all required parent
michael@0 373 // directories starting with the highest-level missing parent.
michael@0 374 FilePath parent_path(full_path.DirName());
michael@0 375 if (parent_path.value() == full_path.value()) {
michael@0 376 if (error) {
michael@0 377 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
michael@0 378 }
michael@0 379 return false;
michael@0 380 }
michael@0 381 if (!CreateDirectoryAndGetError(parent_path, error)) {
michael@0 382 DLOG(WARNING) << "Failed to create one of the parent directories.";
michael@0 383 if (error) {
michael@0 384 DCHECK(*error != base::PLATFORM_FILE_OK);
michael@0 385 }
michael@0 386 return false;
michael@0 387 }
michael@0 388
michael@0 389 if (!::CreateDirectory(full_path_str, NULL)) {
michael@0 390 DWORD error_code = ::GetLastError();
michael@0 391 if (error_code == ERROR_ALREADY_EXISTS && DirectoryExists(full_path)) {
michael@0 392 // This error code ERROR_ALREADY_EXISTS doesn't indicate whether we
michael@0 393 // were racing with someone creating the same directory, or a file
michael@0 394 // with the same path. If DirectoryExists() returns true, we lost the
michael@0 395 // race to create the same directory.
michael@0 396 return true;
michael@0 397 } else {
michael@0 398 if (error)
michael@0 399 *error = base::LastErrorToPlatformFileError(error_code);
michael@0 400 DLOG(WARNING) << "Failed to create directory " << full_path_str
michael@0 401 << ", last error is " << error_code << ".";
michael@0 402 return false;
michael@0 403 }
michael@0 404 } else {
michael@0 405 return true;
michael@0 406 }
michael@0 407 }
michael@0 408
michael@0 409 // TODO(rkc): Work out if we want to handle NTFS junctions here or not, handle
michael@0 410 // them if we do decide to.
michael@0 411 bool IsLink(const FilePath& file_path) {
michael@0 412 return false;
michael@0 413 }
michael@0 414
michael@0 415 bool GetFileInfo(const FilePath& file_path, base::PlatformFileInfo* results) {
michael@0 416 base::ThreadRestrictions::AssertIOAllowed();
michael@0 417
michael@0 418 WIN32_FILE_ATTRIBUTE_DATA attr;
michael@0 419 if (!GetFileAttributesEx(file_path.value().c_str(),
michael@0 420 GetFileExInfoStandard, &attr)) {
michael@0 421 return false;
michael@0 422 }
michael@0 423
michael@0 424 ULARGE_INTEGER size;
michael@0 425 size.HighPart = attr.nFileSizeHigh;
michael@0 426 size.LowPart = attr.nFileSizeLow;
michael@0 427 results->size = size.QuadPart;
michael@0 428
michael@0 429 results->is_directory =
michael@0 430 (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
michael@0 431 results->last_modified = base::Time::FromFileTime(attr.ftLastWriteTime);
michael@0 432 results->last_accessed = base::Time::FromFileTime(attr.ftLastAccessTime);
michael@0 433 results->creation_time = base::Time::FromFileTime(attr.ftCreationTime);
michael@0 434
michael@0 435 return true;
michael@0 436 }
michael@0 437
michael@0 438 FILE* OpenFile(const FilePath& filename, const char* mode) {
michael@0 439 base::ThreadRestrictions::AssertIOAllowed();
michael@0 440 std::wstring w_mode = ASCIIToWide(std::string(mode));
michael@0 441 return _wfsopen(filename.value().c_str(), w_mode.c_str(), _SH_DENYNO);
michael@0 442 }
michael@0 443
michael@0 444 FILE* OpenFile(const std::string& filename, const char* mode) {
michael@0 445 base::ThreadRestrictions::AssertIOAllowed();
michael@0 446 return _fsopen(filename.c_str(), mode, _SH_DENYNO);
michael@0 447 }
michael@0 448
michael@0 449 int ReadFile(const FilePath& filename, char* data, int size) {
michael@0 450 base::ThreadRestrictions::AssertIOAllowed();
michael@0 451 base::win::ScopedHandle file(CreateFile(filename.value().c_str(),
michael@0 452 GENERIC_READ,
michael@0 453 FILE_SHARE_READ | FILE_SHARE_WRITE,
michael@0 454 NULL,
michael@0 455 OPEN_EXISTING,
michael@0 456 FILE_FLAG_SEQUENTIAL_SCAN,
michael@0 457 NULL));
michael@0 458 if (!file)
michael@0 459 return -1;
michael@0 460
michael@0 461 DWORD read;
michael@0 462 if (::ReadFile(file, data, size, &read, NULL) &&
michael@0 463 static_cast<int>(read) == size)
michael@0 464 return read;
michael@0 465 return -1;
michael@0 466 }
michael@0 467
michael@0 468 int WriteFile(const FilePath& filename, const char* data, int size) {
michael@0 469 base::ThreadRestrictions::AssertIOAllowed();
michael@0 470 base::win::ScopedHandle file(CreateFile(filename.value().c_str(),
michael@0 471 GENERIC_WRITE,
michael@0 472 0,
michael@0 473 NULL,
michael@0 474 CREATE_ALWAYS,
michael@0 475 0,
michael@0 476 NULL));
michael@0 477 if (!file) {
michael@0 478 DLOG_GETLASTERROR(WARNING) << "CreateFile failed for path "
michael@0 479 << filename.value();
michael@0 480 return -1;
michael@0 481 }
michael@0 482
michael@0 483 DWORD written;
michael@0 484 BOOL result = ::WriteFile(file, data, size, &written, NULL);
michael@0 485 if (result && static_cast<int>(written) == size)
michael@0 486 return written;
michael@0 487
michael@0 488 if (!result) {
michael@0 489 // WriteFile failed.
michael@0 490 DLOG_GETLASTERROR(WARNING) << "writing file " << filename.value()
michael@0 491 << " failed";
michael@0 492 } else {
michael@0 493 // Didn't write all the bytes.
michael@0 494 DLOG(WARNING) << "wrote" << written << " bytes to "
michael@0 495 << filename.value() << " expected " << size;
michael@0 496 }
michael@0 497 return -1;
michael@0 498 }
michael@0 499
michael@0 500 int AppendToFile(const FilePath& filename, const char* data, int size) {
michael@0 501 base::ThreadRestrictions::AssertIOAllowed();
michael@0 502 base::win::ScopedHandle file(CreateFile(filename.value().c_str(),
michael@0 503 FILE_APPEND_DATA,
michael@0 504 0,
michael@0 505 NULL,
michael@0 506 OPEN_EXISTING,
michael@0 507 0,
michael@0 508 NULL));
michael@0 509 if (!file) {
michael@0 510 DLOG_GETLASTERROR(WARNING) << "CreateFile failed for path "
michael@0 511 << filename.value();
michael@0 512 return -1;
michael@0 513 }
michael@0 514
michael@0 515 DWORD written;
michael@0 516 BOOL result = ::WriteFile(file, data, size, &written, NULL);
michael@0 517 if (result && static_cast<int>(written) == size)
michael@0 518 return written;
michael@0 519
michael@0 520 if (!result) {
michael@0 521 // WriteFile failed.
michael@0 522 DLOG_GETLASTERROR(WARNING) << "writing file " << filename.value()
michael@0 523 << " failed";
michael@0 524 } else {
michael@0 525 // Didn't write all the bytes.
michael@0 526 DLOG(WARNING) << "wrote" << written << " bytes to "
michael@0 527 << filename.value() << " expected " << size;
michael@0 528 }
michael@0 529 return -1;
michael@0 530 }
michael@0 531
michael@0 532 // Gets the current working directory for the process.
michael@0 533 bool GetCurrentDirectory(FilePath* dir) {
michael@0 534 base::ThreadRestrictions::AssertIOAllowed();
michael@0 535
michael@0 536 wchar_t system_buffer[MAX_PATH];
michael@0 537 system_buffer[0] = 0;
michael@0 538 DWORD len = ::GetCurrentDirectory(MAX_PATH, system_buffer);
michael@0 539 if (len == 0 || len > MAX_PATH)
michael@0 540 return false;
michael@0 541 // TODO(evanm): the old behavior of this function was to always strip the
michael@0 542 // trailing slash. We duplicate this here, but it shouldn't be necessary
michael@0 543 // when everyone is using the appropriate FilePath APIs.
michael@0 544 std::wstring dir_str(system_buffer);
michael@0 545 *dir = FilePath(dir_str).StripTrailingSeparators();
michael@0 546 return true;
michael@0 547 }
michael@0 548
michael@0 549 // Sets the current working directory for the process.
michael@0 550 bool SetCurrentDirectory(const FilePath& directory) {
michael@0 551 base::ThreadRestrictions::AssertIOAllowed();
michael@0 552 BOOL ret = ::SetCurrentDirectory(directory.value().c_str());
michael@0 553 return ret != 0;
michael@0 554 }
michael@0 555
michael@0 556 bool NormalizeFilePath(const FilePath& path, FilePath* real_path) {
michael@0 557 base::ThreadRestrictions::AssertIOAllowed();
michael@0 558 FilePath mapped_file;
michael@0 559 if (!NormalizeToNativeFilePath(path, &mapped_file))
michael@0 560 return false;
michael@0 561 // NormalizeToNativeFilePath() will return a path that starts with
michael@0 562 // "\Device\Harddisk...". Helper DevicePathToDriveLetterPath()
michael@0 563 // will find a drive letter which maps to the path's device, so
michael@0 564 // that we return a path starting with a drive letter.
michael@0 565 return DevicePathToDriveLetterPath(mapped_file, real_path);
michael@0 566 }
michael@0 567
michael@0 568 bool DevicePathToDriveLetterPath(const FilePath& nt_device_path,
michael@0 569 FilePath* out_drive_letter_path) {
michael@0 570 base::ThreadRestrictions::AssertIOAllowed();
michael@0 571
michael@0 572 // Get the mapping of drive letters to device paths.
michael@0 573 const int kDriveMappingSize = 1024;
michael@0 574 wchar_t drive_mapping[kDriveMappingSize] = {'\0'};
michael@0 575 if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping)) {
michael@0 576 DLOG(ERROR) << "Failed to get drive mapping.";
michael@0 577 return false;
michael@0 578 }
michael@0 579
michael@0 580 // The drive mapping is a sequence of null terminated strings.
michael@0 581 // The last string is empty.
michael@0 582 wchar_t* drive_map_ptr = drive_mapping;
michael@0 583 wchar_t device_path_as_string[MAX_PATH];
michael@0 584 wchar_t drive[] = L" :";
michael@0 585
michael@0 586 // For each string in the drive mapping, get the junction that links
michael@0 587 // to it. If that junction is a prefix of |device_path|, then we
michael@0 588 // know that |drive| is the real path prefix.
michael@0 589 while (*drive_map_ptr) {
michael@0 590 drive[0] = drive_map_ptr[0]; // Copy the drive letter.
michael@0 591
michael@0 592 if (QueryDosDevice(drive, device_path_as_string, MAX_PATH)) {
michael@0 593 FilePath device_path(device_path_as_string);
michael@0 594 if (device_path == nt_device_path ||
michael@0 595 device_path.IsParent(nt_device_path)) {
michael@0 596 *out_drive_letter_path = FilePath(drive +
michael@0 597 nt_device_path.value().substr(wcslen(device_path_as_string)));
michael@0 598 return true;
michael@0 599 }
michael@0 600 }
michael@0 601 // Move to the next drive letter string, which starts one
michael@0 602 // increment after the '\0' that terminates the current string.
michael@0 603 while (*drive_map_ptr++);
michael@0 604 }
michael@0 605
michael@0 606 // No drive matched. The path does not start with a device junction
michael@0 607 // that is mounted as a drive letter. This means there is no drive
michael@0 608 // letter path to the volume that holds |device_path|, so fail.
michael@0 609 return false;
michael@0 610 }
michael@0 611
michael@0 612 bool NormalizeToNativeFilePath(const FilePath& path, FilePath* nt_path) {
michael@0 613 base::ThreadRestrictions::AssertIOAllowed();
michael@0 614 // In Vista, GetFinalPathNameByHandle() would give us the real path
michael@0 615 // from a file handle. If we ever deprecate XP, consider changing the
michael@0 616 // code below to a call to GetFinalPathNameByHandle(). The method this
michael@0 617 // function uses is explained in the following msdn article:
michael@0 618 // http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
michael@0 619 base::win::ScopedHandle file_handle(
michael@0 620 ::CreateFile(path.value().c_str(),
michael@0 621 GENERIC_READ,
michael@0 622 kFileShareAll,
michael@0 623 NULL,
michael@0 624 OPEN_EXISTING,
michael@0 625 FILE_ATTRIBUTE_NORMAL,
michael@0 626 NULL));
michael@0 627 if (!file_handle)
michael@0 628 return false;
michael@0 629
michael@0 630 // Create a file mapping object. Can't easily use MemoryMappedFile, because
michael@0 631 // we only map the first byte, and need direct access to the handle. You can
michael@0 632 // not map an empty file, this call fails in that case.
michael@0 633 base::win::ScopedHandle file_map_handle(
michael@0 634 ::CreateFileMapping(file_handle.Get(),
michael@0 635 NULL,
michael@0 636 PAGE_READONLY,
michael@0 637 0,
michael@0 638 1, // Just one byte. No need to look at the data.
michael@0 639 NULL));
michael@0 640 if (!file_map_handle)
michael@0 641 return false;
michael@0 642
michael@0 643 // Use a view of the file to get the path to the file.
michael@0 644 void* file_view = MapViewOfFile(file_map_handle.Get(),
michael@0 645 FILE_MAP_READ, 0, 0, 1);
michael@0 646 if (!file_view)
michael@0 647 return false;
michael@0 648
michael@0 649 // The expansion of |path| into a full path may make it longer.
michael@0 650 // GetMappedFileName() will fail if the result is longer than MAX_PATH.
michael@0 651 // Pad a bit to be safe. If kMaxPathLength is ever changed to be less
michael@0 652 // than MAX_PATH, it would be nessisary to test that GetMappedFileName()
michael@0 653 // not return kMaxPathLength. This would mean that only part of the
michael@0 654 // path fit in |mapped_file_path|.
michael@0 655 const int kMaxPathLength = MAX_PATH + 10;
michael@0 656 wchar_t mapped_file_path[kMaxPathLength];
michael@0 657 bool success = false;
michael@0 658 HANDLE cp = GetCurrentProcess();
michael@0 659 if (::GetMappedFileNameW(cp, file_view, mapped_file_path, kMaxPathLength)) {
michael@0 660 *nt_path = FilePath(mapped_file_path);
michael@0 661 success = true;
michael@0 662 }
michael@0 663 ::UnmapViewOfFile(file_view);
michael@0 664 return success;
michael@0 665 }
michael@0 666
michael@0 667 int GetMaximumPathComponentLength(const FilePath& path) {
michael@0 668 base::ThreadRestrictions::AssertIOAllowed();
michael@0 669
michael@0 670 wchar_t volume_path[MAX_PATH];
michael@0 671 if (!GetVolumePathNameW(path.NormalizePathSeparators().value().c_str(),
michael@0 672 volume_path,
michael@0 673 arraysize(volume_path))) {
michael@0 674 return -1;
michael@0 675 }
michael@0 676
michael@0 677 DWORD max_length = 0;
michael@0 678 if (!GetVolumeInformationW(volume_path, NULL, 0, NULL, &max_length, NULL,
michael@0 679 NULL, 0)) {
michael@0 680 return -1;
michael@0 681 }
michael@0 682
michael@0 683 // Length of |path| with path separator appended.
michael@0 684 size_t prefix = path.StripTrailingSeparators().value().size() + 1;
michael@0 685 // The whole path string must be shorter than MAX_PATH. That is, it must be
michael@0 686 // prefix + component_length < MAX_PATH (or equivalently, <= MAX_PATH - 1).
michael@0 687 int whole_path_limit = std::max(0, MAX_PATH - 1 - static_cast<int>(prefix));
michael@0 688 return std::min(whole_path_limit, static_cast<int>(max_length));
michael@0 689 }
michael@0 690
michael@0 691 } // namespace file_util
michael@0 692
michael@0 693 namespace base {
michael@0 694 namespace internal {
michael@0 695
michael@0 696 bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) {
michael@0 697 ThreadRestrictions::AssertIOAllowed();
michael@0 698
michael@0 699 // NOTE: I suspect we could support longer paths, but that would involve
michael@0 700 // analyzing all our usage of files.
michael@0 701 if (from_path.value().length() >= MAX_PATH ||
michael@0 702 to_path.value().length() >= MAX_PATH) {
michael@0 703 return false;
michael@0 704 }
michael@0 705 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(),
michael@0 706 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0)
michael@0 707 return true;
michael@0 708
michael@0 709 // Keep the last error value from MoveFileEx around in case the below
michael@0 710 // fails.
michael@0 711 bool ret = false;
michael@0 712 DWORD last_error = ::GetLastError();
michael@0 713
michael@0 714 if (DirectoryExists(from_path)) {
michael@0 715 // MoveFileEx fails if moving directory across volumes. We will simulate
michael@0 716 // the move by using Copy and Delete. Ideally we could check whether
michael@0 717 // from_path and to_path are indeed in different volumes.
michael@0 718 ret = internal::CopyAndDeleteDirectory(from_path, to_path);
michael@0 719 }
michael@0 720
michael@0 721 if (!ret) {
michael@0 722 // Leave a clue about what went wrong so that it can be (at least) picked
michael@0 723 // up by a PLOG entry.
michael@0 724 ::SetLastError(last_error);
michael@0 725 }
michael@0 726
michael@0 727 return ret;
michael@0 728 }
michael@0 729
michael@0 730 bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) {
michael@0 731 ThreadRestrictions::AssertIOAllowed();
michael@0 732
michael@0 733 // NOTE: I suspect we could support longer paths, but that would involve
michael@0 734 // analyzing all our usage of files.
michael@0 735 if (from_path.value().length() >= MAX_PATH ||
michael@0 736 to_path.value().length() >= MAX_PATH) {
michael@0 737 return false;
michael@0 738 }
michael@0 739 return (::CopyFile(from_path.value().c_str(), to_path.value().c_str(),
michael@0 740 false) != 0);
michael@0 741 }
michael@0 742
michael@0 743 bool CopyAndDeleteDirectory(const FilePath& from_path,
michael@0 744 const FilePath& to_path) {
michael@0 745 ThreadRestrictions::AssertIOAllowed();
michael@0 746 if (CopyDirectory(from_path, to_path, true)) {
michael@0 747 if (DeleteFile(from_path, true))
michael@0 748 return true;
michael@0 749
michael@0 750 // Like Move, this function is not transactional, so we just
michael@0 751 // leave the copied bits behind if deleting from_path fails.
michael@0 752 // If to_path exists previously then we have already overwritten
michael@0 753 // it by now, we don't get better off by deleting the new bits.
michael@0 754 }
michael@0 755 return false;
michael@0 756 }
michael@0 757
michael@0 758 } // namespace internal
michael@0 759 } // namespace base

mercurial