Index: base/file_util_unittest.cc |
diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc |
index 05a1aeedc09e47c3a1dc8f304fa4871074bb5802..d66a4261e50fb5be620713fa67c5e1a7bfbafb59 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) |