diff options
author | skerner@chromium.org <skerner@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-09 22:56:48 +0000 |
---|---|---|
committer | skerner@chromium.org <skerner@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-09 22:56:48 +0000 |
commit | 6f5f4322d6688abd29d0253a6a8201fb7b1c103d (patch) | |
tree | 1ceb35e4715990b18b9f7f6327c5072f7a699fe4 /base | |
parent | a53bb6f7db58fa59fb2dd3b6508b6acc0d1d44ad (diff) | |
download | chromium_src-6f5f4322d6688abd29d0253a6a8201fb7b1c103d.zip chromium_src-6f5f4322d6688abd29d0253a6a8201fb7b1c103d.tar.gz chromium_src-6f5f4322d6688abd29d0253a6a8201fb7b1c103d.tar.bz2 |
Give the extension unpacker process a junction/symlink free path to the unpack directory.
BUG=35198,13044
TEST=FileUtilTest.NormalizeFilePathBasic,FileUtilTest. NormalizeFilePathReparsePoints,FileUtilTest.NormalizeFilePathSymlinks
Review URL: http://codereview.chromium.org/2088006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49337 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/data/valgrind/base_unittests.gtest_mac.txt | 6 | ||||
-rw-r--r-- | base/file_util.h | 13 | ||||
-rw-r--r-- | base/file_util_posix.cc | 29 | ||||
-rw-r--r-- | base/file_util_unittest.cc | 309 | ||||
-rw-r--r-- | base/file_util_win.cc | 125 |
5 files changed, 437 insertions, 45 deletions
diff --git a/base/data/valgrind/base_unittests.gtest_mac.txt b/base/data/valgrind/base_unittests.gtest_mac.txt index 6c7baa4..d78c20d 100644 --- a/base/data/valgrind/base_unittests.gtest_mac.txt +++ b/base/data/valgrind/base_unittests.gtest_mac.txt @@ -1,6 +1,6 @@ # Fails on Valgrind/Mac, see http://crbug.com/43972 ConditionVariableTest.LargeFastTaskTest -# Fails on Valgrind/Mac due to missing syscall wrapper. -# See http://crbug.com/44001 -FileUtilTest.RealPath +# Fails on Valgrind/Mac due to missing syscall wrapper +# for the symlink() syscall. See http://crbug.com/44001 +FileUtilTest.NormalizeFilePathSymlinks diff --git a/base/file_util.h b/base/file_util.h index 414f775..04afdec 100644 --- a/base/file_util.h +++ b/base/file_util.h @@ -281,12 +281,13 @@ bool IsDot(const FilePath& path); // Returns true if the given path's base name is "..". bool IsDotDot(const FilePath& path); -#if defined(OS_POSIX) -// Set |real_path| to |path| with symbolic links expanded. -// Windows support (expanding junctions) comming soon: -// http://crbug.com/13044 -bool RealPath(const FilePath& path, FilePath* real_path); -#endif +// Sets |real_path| to |path| with symbolic links and junctions expanded. +// On windows, make sure the path starts with a lettered drive. +// |path| must reference a file. Function will fail if |path| points to +// a directory or to a nonexistent path. On windows, this function will +// fail if |path| is a junction or symlink that points to an empty file, +// or if |real_path| would be longer than MAX_PATH characters. +bool NormalizeFilePath(const FilePath& path, FilePath* real_path); // Used to hold information about a given file path. See GetFileInfo below. struct FileInfo { diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc index bd1711b..441ecc8 100644 --- a/base/file_util_posix.cc +++ b/base/file_util_posix.cc @@ -44,6 +44,20 @@ namespace file_util { +namespace { + +// Helper for NormalizeFilePath(), defined below. +bool RealPath(const FilePath& path, FilePath* real_path) { + FilePath::CharType buf[PATH_MAX]; + if (!realpath(path.value().c_str(), buf)) + return false; + + *real_path = FilePath(buf); + return true; +} + +} // namespace + #if defined(OS_OPENBSD) || defined(OS_FREEBSD) || \ (defined(OS_MACOSX) && \ MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) @@ -726,12 +740,19 @@ bool HasFileBeenModifiedSince(const FileEnumerator::FindInfo& find_info, return find_info.stat.st_mtime >= cutoff_time.ToTimeT(); } -bool RealPath(const FilePath& path, FilePath* real_path) { - FilePath::CharType buf[PATH_MAX]; - if (!realpath(path.value().c_str(), buf)) +bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) { + FilePath real_path_result; + if (!RealPath(path, &real_path_result)) return false; - *real_path = FilePath(buf); + // To be consistant with windows, fail if |real_path_result| is a + // directory. + stat_wrapper_t file_info; + if (CallStat(real_path_result.value().c_str(), &file_info) != 0 || + S_ISDIR(file_info.st_mode)) + return false; + + *normalized_path = real_path_result; return true; } diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc index 05a1aee..d66a426 100644 --- a/base/file_util_unittest.cc +++ b/base/file_util_unittest.cc @@ -6,6 +6,7 @@ #if defined(OS_WIN) #include <windows.h> +#include <winioctl.h> #include <shellapi.h> #include <shlobj.h> #include <tchar.h> @@ -21,6 +22,7 @@ #include "base/logging.h" #include "base/path_service.h" #include "base/platform_thread.h" +#include "base/scoped_handle.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" @@ -31,6 +33,81 @@ namespace { +// To test that file_util::Normalize FilePath() deals with NTFS reparse points +// correctly, we need functions to create and delete reparse points. +#if defined(OS_WIN) +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +// Sets a reparse point. |source| will now point to |target|. Returns true if +// the call succeeds, false otherwise. +bool SetReparsePoint(HANDLE source, const FilePath& target_path) { + std::wstring kPathPrefix = L"\\??\\"; + std::wstring target_str; + // The juction will not work if the target path does not start with \??\ . + if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size())) + target_str += kPathPrefix; + target_str += target_path.value(); + const wchar_t* target = target_str.c_str(); + USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]); + char buffer[2000] = {0}; + DWORD returned; + + REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer); + + data->ReparseTag = 0xa0000003; + memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2); + + data->MountPointReparseBuffer.SubstituteNameLength = size_target; + data->MountPointReparseBuffer.PrintNameOffset = size_target + 2; + data->ReparseDataLength = size_target + 4 + 8; + + int data_size = data->ReparseDataLength + 8; + + if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size, + NULL, 0, &returned, NULL)) { + return false; + } + return true; +} + +// Delete the reparse point referenced by |source|. Returns true if the call +// succeeds, false otherwise. +bool DeleteReparsePoint(HANDLE source) { + DWORD returned; + REPARSE_DATA_BUFFER data = {0}; + data.ReparseTag = 0xa0000003; + if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0, + &returned, NULL)) { + return false; + } + return true; +} +#endif + const wchar_t bogus_content[] = L"I'm cannon fodder."; const file_util::FileEnumerator::FILE_TYPE FILES_AND_DIRECTORIES = @@ -387,54 +464,230 @@ TEST_F(FileUtilTest, FileAndDirectorySize) { EXPECT_EQ(size_f1 + size_f2 + 3, computed_size); } +TEST_F(FileUtilTest, NormalizeFilePathBasic) { + // Create a directory under the test dir. Because we create it, + // we know it is not a link. + FilePath file_a_path = test_dir_.Append(FPL("file_a")); + FilePath dir_path = test_dir_.Append(FPL("dir")); + FilePath file_b_path = dir_path.Append(FPL("file_b")); + file_util::CreateDirectory(dir_path); + + FilePath normalized_file_a_path, normalized_file_b_path; + ASSERT_FALSE(file_util::PathExists(file_a_path)); + ASSERT_FALSE(file_util::NormalizeFilePath(file_a_path, + &normalized_file_a_path)) + << "NormalizeFilePath() should fail on nonexistant paths."; + + CreateTextFile(file_a_path, bogus_content); + ASSERT_TRUE(file_util::PathExists(file_a_path)); + ASSERT_TRUE(file_util::NormalizeFilePath(file_a_path, + &normalized_file_a_path)); + + CreateTextFile(file_b_path, bogus_content); + ASSERT_TRUE(file_util::PathExists(file_b_path)); + ASSERT_TRUE(file_util::NormalizeFilePath(file_b_path, + &normalized_file_b_path)); + + // Beacuse this test created |dir_path|, we know it is not a link + // or junction. So, the real path of the directory holding file a + // must be the parent of the path holding file b. + ASSERT_TRUE(normalized_file_a_path.DirName() + .IsParent(normalized_file_b_path.DirName())); +} + +#if defined(OS_WIN) + +TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) { + // Build the following directory structure: + // + // test_dir_ + // |-> base_a + // | |-> sub_a + // | |-> file.txt + // | |-> long_name___... (Very long name.) + // | |-> sub_long + // | |-> deep.txt + // |-> base_b + // |-> to_sub_a (reparse point to test_dir_\base_a\sub_a) + // |-> to_base_b (reparse point to test_dir_\base_b) + // |-> to_sub_long (reparse point to test_dir_\sub_a\long_name_\sub_long) + + FilePath base_a = test_dir_.Append(FPL("base_a")); + ASSERT_TRUE(file_util::CreateDirectory(base_a)); + + FilePath sub_a = base_a.Append(FPL("sub_a")); + ASSERT_TRUE(file_util::CreateDirectory(sub_a)); + + FilePath file_txt = sub_a.Append(FPL("file.txt")); + CreateTextFile(file_txt, bogus_content); + + // Want a directory whose name is long enough to make the path to the file + // inside just under MAX_PATH chars. This will be used to test that when + // a junction expands to a path over MAX_PATH chars in length, + // NormalizeFilePath() fails without crashing. + FilePath sub_long_rel(FPL("sub_long")); + FilePath deep_txt(FPL("deep.txt")); + + int target_length = MAX_PATH; + target_length -= (sub_a.value().length() + 1); // +1 for the sepperator '\'. + target_length -= (sub_long_rel.Append(deep_txt).value().length() + 1); + // Without making the path a bit shorter, CreateDirectory() fails. + // the resulting path is still long enough to hit the failing case in + // NormalizePath(). + const int kCreateDirLimit = 4; + target_length -= kCreateDirLimit; + FilePath::StringType long_name_str = FPL("long_name_"); + long_name_str.resize(target_length, '_'); + + FilePath long_name = sub_a.Append(FilePath(long_name_str)); + FilePath deep_file = long_name.Append(sub_long_rel).Append(deep_txt); + ASSERT_EQ(MAX_PATH - kCreateDirLimit, deep_file.value().length()); + + FilePath sub_long = deep_file.DirName(); + ASSERT_TRUE(file_util::CreateDirectory(sub_long)); + CreateTextFile(deep_file, bogus_content); + + FilePath base_b = test_dir_.Append(FPL("base_b")); + ASSERT_TRUE(file_util::CreateDirectory(base_b)); + + FilePath to_sub_a = base_b.Append(FPL("to_sub_a")); + ASSERT_TRUE(file_util::CreateDirectory(to_sub_a)); + ScopedHandle reparse_to_sub_a( + ::CreateFile(to_sub_a.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_NE(INVALID_HANDLE_VALUE, reparse_to_sub_a.Get()); + ASSERT_TRUE(SetReparsePoint(reparse_to_sub_a, sub_a)); + + FilePath to_base_b = base_b.Append(FPL("to_base_b")); + ASSERT_TRUE(file_util::CreateDirectory(to_base_b)); + ScopedHandle reparse_to_base_b( + ::CreateFile(to_base_b.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_NE(INVALID_HANDLE_VALUE, reparse_to_base_b.Get()); + ASSERT_TRUE(SetReparsePoint(reparse_to_base_b, base_b)); + + FilePath to_sub_long = base_b.Append(FPL("to_sub_long")); + ASSERT_TRUE(file_util::CreateDirectory(to_sub_long)); + ScopedHandle reparse_to_sub_long( + ::CreateFile(to_sub_long.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_NE(INVALID_HANDLE_VALUE, reparse_to_sub_long.Get()); + ASSERT_TRUE(SetReparsePoint(reparse_to_sub_long, sub_long)); + + // Normalize a junction free path: base_a\sub_a\file.txt . + FilePath normalized_path; + ASSERT_TRUE(file_util::NormalizeFilePath(file_txt, &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // Check that the path base_b\to_sub_a\file.txt can be normalized to exclude + // the junction to_sub_a. + ASSERT_TRUE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // Check that the path base_b\to_base_b\to_base_b\to_sub_a\file.txt can be + // normalized to exclude junctions to_base_b and to_sub_a . + ASSERT_TRUE(file_util::NormalizeFilePath(base_b.Append(FPL("to_base_b")) + .Append(FPL("to_base_b")) + .Append(FPL("to_sub_a")) + .Append(FPL("file.txt")), + &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // A long enough path will cause NormalizeFilePath() to fail. Make a long + // path using to_base_b many times, and check that paths long enough to fail + // do not cause a crash. + FilePath long_path = base_b; + const int kLengthLimit = MAX_PATH + 200; + while (long_path.value().length() <= kLengthLimit) { + long_path = long_path.Append(FPL("to_base_b")); + } + long_path = long_path.Append(FPL("to_sub_a")) + .Append(FPL("file.txt")); + + ASSERT_FALSE(file_util::NormalizeFilePath(long_path, &normalized_path)); + + // Normalizing the junction to deep.txt should fail, because the expanded + // path to deep.txt is longer than MAX_PATH. + ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_long.Append(deep_txt), + &normalized_path)); + + // Delete the reparse points, and see that NormalizeFilePath() fails + // to traverse them. + ASSERT_TRUE(DeleteReparsePoint(reparse_to_sub_a)); + ASSERT_TRUE(DeleteReparsePoint(reparse_to_base_b)); + ASSERT_TRUE(DeleteReparsePoint(reparse_to_sub_long)); + + ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + &normalized_path)); +} + +#endif // defined(OS_WIN) + +// The following test of NormalizeFilePath() require that we create a symlink. +// This can not be done on windows before vista. On vista, creating a symlink +// requires privilege "SeCreateSymbolicLinkPrivilege". +// TODO(skerner): Investigate the possibility of giving base_unittests the +// privileges required to create a symlink. #if defined(OS_POSIX) -TEST_F(FileUtilTest, RealPath) { - // Get the real test directory, in case some future change to the - // test setup makes the path to test_dir_ include a symlink. - FilePath real_test_dir; - ASSERT_TRUE(file_util::RealPath(test_dir_, &real_test_dir)); - FilePath real_path; - ASSERT_TRUE(file_util::RealPath(real_test_dir, &real_path)); - ASSERT_TRUE(real_test_dir == real_path); +bool MakeSymlink(const FilePath& link_to, const FilePath& link_from) { + return (symlink(link_to.value().c_str(), link_from.value().c_str()) == 0); +} + +TEST_F(FileUtilTest, NormalizeFilePathSymlinks) { + FilePath normalized_path; // Link one file to another. - FilePath link_from = real_test_dir.Append(FPL("from_file")); - FilePath link_to = real_test_dir.Append(FPL("to_file")); + FilePath link_from = test_dir_.Append(FPL("from_file")); + FilePath link_to = test_dir_.Append(FPL("to_file")); CreateTextFile(link_to, bogus_content); - ASSERT_EQ(0, symlink(link_to.value().c_str(), link_from.value().c_str())) + ASSERT_TRUE(MakeSymlink(link_to, link_from)) << "Failed to create file symlink."; - // Check that RealPath sees the link. - ASSERT_TRUE(file_util::RealPath(link_from, &real_path)); + // Check that NormalizeFilePath sees the link. + ASSERT_TRUE(file_util::NormalizeFilePath(link_from, &normalized_path)); ASSERT_TRUE(link_to != link_from); - ASSERT_TRUE(link_to == real_path); - + ASSERT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + ASSERT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); // Link to a directory. - link_from = real_test_dir.Append(FPL("from_dir")); - link_to = real_test_dir.Append(FPL("to_dir")); + link_from = test_dir_.Append(FPL("from_dir")); + link_to = test_dir_.Append(FPL("to_dir")); file_util::CreateDirectory(link_to); - ASSERT_EQ(0, symlink(link_to.value().c_str(), link_from.value().c_str())) + ASSERT_TRUE(MakeSymlink(link_to, link_from)) << "Failed to create directory symlink."; - ASSERT_TRUE(file_util::RealPath(link_from, &real_path)); - ASSERT_TRUE(link_to != link_from); - ASSERT_TRUE(link_to == real_path); - + ASSERT_FALSE(file_util::NormalizeFilePath(link_from, &normalized_path)) + << "Links to directories should return false."; - // Test that a loop in the links causes RealPath() to return false. - link_from = real_test_dir.Append(FPL("link_a")); - link_to = real_test_dir.Append(FPL("link_b")); - ASSERT_EQ(0, symlink(link_to.value().c_str(), link_from.value().c_str())) + // Test that a loop in the links causes NormalizeFilePath() to return false. + link_from = test_dir_.Append(FPL("link_a")); + link_to = test_dir_.Append(FPL("link_b")); + ASSERT_TRUE(MakeSymlink(link_to, link_from)) << "Failed to create loop symlink a."; - ASSERT_EQ(0, symlink(link_from.value().c_str(), link_to.value().c_str())) + ASSERT_TRUE(MakeSymlink(link_from, link_to)) << "Failed to create loop symlink b."; // Infinite loop! - ASSERT_FALSE(file_util::RealPath(link_from, &real_path)); + ASSERT_FALSE(file_util::NormalizeFilePath(link_from, &normalized_path)); } #endif // defined(OS_POSIX) diff --git a/base/file_util_win.cc b/base/file_util_win.cc index fcad39c..dc15ea0 100644 --- a/base/file_util_win.cc +++ b/base/file_util_win.cc @@ -6,6 +6,7 @@ #include <windows.h> #include <propvarutil.h> +#include <psapi.h> #include <shellapi.h> #include <shlobj.h> #include <time.h> @@ -21,6 +22,49 @@ namespace file_util { +namespace { + +// Helper for NormalizeFilePath(), defined below. +bool DevicePathToDriveLetterPath(const FilePath& device_path, + FilePath* drive_letter_path) { + // Get the mapping of drive letters to device paths. + const int kDriveMappingSize = 1024; + wchar_t drive_mapping[kDriveMappingSize] = {'\0'}; + if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping)) { + LOG(ERROR) << "Failed to get drive mapping."; + return false; + } + + // The drive mapping is a sequence of null terminated strings. + // The last string is empty. + wchar_t* drive_map_ptr = drive_mapping; + wchar_t device_name[MAX_PATH]; + wchar_t drive[] = L" :"; + + // For each string in the drive mapping, get the junction that links + // to it. If that junction is a prefix of |device_path|, then we + // know that |drive| is the real path prefix. + while(*drive_map_ptr) { + drive[0] = drive_map_ptr[0]; // Copy the drive letter. + + if (QueryDosDevice(drive, device_name, MAX_PATH) && + StartsWith(device_path.value(), device_name, true)) { + *drive_letter_path = FilePath(drive + + device_path.value().substr(wcslen(device_name))); + return true; + } + // Move to the next drive letter string, which starts one + // increment after the '\0' that terminates the current string. + while(*drive_map_ptr++); + } + + // No drive matched. The path does not start with a device junction. + *drive_letter_path = device_path; + return true; +} + +} // namespace + std::wstring GetDirectoryFromPath(const std::wstring& path) { wchar_t path_buffer[MAX_PATH]; wchar_t* file_ptr = NULL; @@ -137,10 +181,14 @@ bool Move(const FilePath& from_path, const FilePath& to_path) { bool ReplaceFile(const FilePath& from_path, const FilePath& to_path) { // Make sure that the target file exists. - HANDLE target_file = ::CreateFile(to_path.value().c_str(), 0, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, CREATE_NEW, - FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE target_file = ::CreateFile( + to_path.value().c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); if (target_file != INVALID_HANDLE_VALUE) ::CloseHandle(target_file); // When writing to a network share, we may not be able to change the ACLs. @@ -892,4 +940,73 @@ bool HasFileBeenModifiedSince(const FileEnumerator::FindInfo& find_info, return result == 1 || result == 0; } +bool NormalizeFilePath(const FilePath& path, FilePath* real_path) { + ScopedHandle path_handle( + ::CreateFile(path.value().c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL)); + if (path_handle == INVALID_HANDLE_VALUE) + return false; + + // In Vista, GetFinalPathNameByHandle() would give us the real path + // from a file handle. If we ever deprecate XP, consider changing the + // code below to a call to GetFinalPathNameByHandle(). The method this + // function uses is explained in the following msdn article: + // http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx + DWORD file_size_high = 0; + DWORD file_size_low = ::GetFileSize(path_handle.Get(), &file_size_high); + if (file_size_low == 0 && file_size_high == 0) { + // It is not possible to map an empty file. + LOG(ERROR) << "NormalizeFilePath failed: Empty file."; + return false; + } + + // Create a file mapping object. Can't easily use MemoryMappedFile, because + // we only map the first byte, and need direct access to the handle. + ScopedHandle file_map_handle( + ::CreateFileMapping(path_handle.Get(), + NULL, + PAGE_READONLY, + 0, + 1, // Just one byte. No need to look at the data. + NULL)); + + if (file_map_handle == INVALID_HANDLE_VALUE) + return false; + + // Use a view of the file to get the path to the file. + void* file_view = MapViewOfFile( + file_map_handle.Get(), FILE_MAP_READ, 0, 0, 1); + if (!file_view) + return false; + + bool success = false; + + // The expansion of |path| into a full path may make it longer. + // GetMappedFileName() will fail if the result is longer than MAX_PATH. + // Pad a bit to be safe. If kMaxPathLength is ever changed to be less + // than MAX_PATH, it would be nessisary to test that GetMappedFileName() + // not return kMaxPathLength. This would mean that only part of the + // path fit in |mapped_file_path|. + const int kMaxPathLength = MAX_PATH + 10; + wchar_t mapped_file_path[kMaxPathLength]; + if (::GetMappedFileName(GetCurrentProcess(), + file_view, + mapped_file_path, + kMaxPathLength)) { + // GetMappedFileName() will return a path that starts with + // "\Device\Harddisk...". Helper DevicePathToDriveLetterPath() + // will find a drive letter which maps to the path's device, so + // that we return a path starting with a drive letter. + FilePath mapped_file(mapped_file_path); + success = DevicePathToDriveLetterPath(mapped_file, real_path); + } + UnmapViewOfFile(file_view); + return success; +} + } // namespace file_util |