|  | // Copyright 2016 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #ifndef WIN32_LEAN_AND_MEAN | 
|  | #define WIN32_LEAN_AND_MEAN | 
|  | #endif | 
|  | #include <windows.h> | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <memory> | 
|  | #include <string> | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "src/main/cpp/util/file.h" | 
|  | #include "src/main/cpp/util/file_platform.h" | 
|  | #include "src/main/cpp/util/path.h" | 
|  | #include "src/main/cpp/util/path_platform.h" | 
|  | #include "src/main/cpp/util/strings.h" | 
|  | #include "src/main/native/windows/file.h" | 
|  | #include "src/main/native/windows/util.h" | 
|  | #include "src/test/cpp/util/test_util.h" | 
|  | #include "src/test/cpp/util/windows_test_util.h" | 
|  |  | 
|  | #if !defined(_WIN32) && !defined(__CYGWIN__) | 
|  | #error("This test should only be run on Windows") | 
|  | #endif  // !defined(_WIN32) && !defined(__CYGWIN__) | 
|  |  | 
|  | #define TOSTRING(x) #x | 
|  |  | 
|  | namespace blaze_util { | 
|  |  | 
|  | using bazel::windows::CreateJunction; | 
|  | using bazel::windows::CreateJunctionResult; | 
|  | using std::string; | 
|  | using std::unique_ptr; | 
|  | using std::wstring; | 
|  |  | 
|  | class FileWindowsTest : public ::testing::Test { | 
|  | public: | 
|  | void TearDown() override { DeleteAllUnder(GetTestTmpDirW()); } | 
|  | }; | 
|  |  | 
|  | // This is a macro so the assertions will have the correct line number. | 
|  | #define GET_TEST_TMPDIR(/* string& */ result)                            \ | 
|  | {                                                                      \ | 
|  | char buf[MAX_PATH] = {0};                                            \ | 
|  | DWORD len = ::GetEnvironmentVariableA("TEST_TMPDIR", buf, MAX_PATH); \ | 
|  | result = buf;                                                        \ | 
|  | ASSERT_GT(result.size(), 0);                                         \ | 
|  | } | 
|  |  | 
|  | #define CREATE_JUNCTION(/* const string& */ name, /* const string& */ target) \ | 
|  | {                                                                           \ | 
|  | wstring wname;                                                            \ | 
|  | wstring wtarget;                                                          \ | 
|  | EXPECT_TRUE(AsWindowsPath(name, &wname, nullptr));                        \ | 
|  | EXPECT_TRUE(AsWindowsPath(target, &wtarget, nullptr));                    \ | 
|  | EXPECT_EQ(CreateJunction(wname, wtarget, nullptr),                        \ | 
|  | CreateJunctionResult::kSuccess);                                \ | 
|  | } | 
|  |  | 
|  | // Asserts that dir1 can be created with some content, and dir2 doesn't exist. | 
|  | static void AssertTearDown(const WCHAR* dir1, const WCHAR* dir2) { | 
|  | wstring wtmpdir(GetTestTmpDirW()); | 
|  | wstring dir1str(wtmpdir + L"\\" + dir1); | 
|  | wstring subdir(dir1str + L"\\subdir"); | 
|  | wstring wfile(subdir + L"\\hello.txt"); | 
|  | EXPECT_TRUE(::CreateDirectoryW(dir1str.c_str(), nullptr)); | 
|  | EXPECT_TRUE(::CreateDirectoryW(subdir.c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDummyFile(wfile)); | 
|  | EXPECT_NE(::GetFileAttributesW(wfile.c_str()), INVALID_FILE_ATTRIBUTES); | 
|  | ASSERT_EQ(::GetFileAttributesW((wtmpdir + L"\\" + dir2).c_str()), | 
|  | INVALID_FILE_ATTRIBUTES); | 
|  | } | 
|  |  | 
|  | // One half of the teardown test: assert that test.teardown.b was cleaned up. | 
|  | TEST_F(FileWindowsTest, TestTearDownA) { | 
|  | AssertTearDown(L"test.teardown.a", L"test.teardown.b"); | 
|  | } | 
|  |  | 
|  | // Other half of the teardown test: assert that test.teardown.a was cleaned up. | 
|  | TEST_F(FileWindowsTest, TestTearDownB) { | 
|  | AssertTearDown(L"test.teardown.b", L"test.teardown.a"); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestMsysRootRetrieval) { | 
|  | wstring actual; | 
|  |  | 
|  | // We just need "bin/<something>" or "usr/bin/<something>". | 
|  | // Forward slashes are converted to backslashes. | 
|  | SetEnvironmentVariableA("BAZEL_SH", "c:/foo\\bin/some_bash.exe"); | 
|  |  | 
|  | string error; | 
|  | ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error)); | 
|  | EXPECT_TRUE(error.find("Unix-style") != string::npos); | 
|  |  | 
|  | SetEnvironmentVariableA("BAZEL_SH", "c:/tools/msys64/usr/bin/bash.exe"); | 
|  | ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error)); | 
|  | EXPECT_TRUE(error.find("Unix-style") != string::npos); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestPathExistsWindows) { | 
|  | ASSERT_FALSE(PathExists("")); | 
|  | ASSERT_TRUE(PathExists(".")); | 
|  | ASSERT_FALSE(PathExists("non.existent")); | 
|  | ASSERT_TRUE(PathExists("/dev/null")); | 
|  | ASSERT_TRUE(PathExists("Nul")); | 
|  |  | 
|  | string tmpdir; | 
|  | GET_TEST_TMPDIR(tmpdir); | 
|  | ASSERT_TRUE(PathExists(tmpdir)); | 
|  |  | 
|  | // Create junction target that'll double as the fake msys root. | 
|  | string fake_msys_root(tmpdir + "/blah"); | 
|  | ASSERT_EQ(0, mkdir(fake_msys_root.c_str())); | 
|  | ASSERT_TRUE(PathExists(fake_msys_root)); | 
|  |  | 
|  | // Set the BAZEL_SH root so we can resolve MSYS paths. | 
|  | SetEnvironmentVariableA("BAZEL_SH", | 
|  | (fake_msys_root + "/bin/fake_bash.exe").c_str()); | 
|  |  | 
|  | // Create a junction pointing to an existing directory. | 
|  | CREATE_JUNCTION(tmpdir + "/junc1", fake_msys_root); | 
|  | ASSERT_TRUE(PathExists(fake_msys_root)); | 
|  | ASSERT_TRUE(PathExists(JoinPath(tmpdir, "junc1"))); | 
|  |  | 
|  | // Create a junction pointing to a non-existent directory. | 
|  | CREATE_JUNCTION(tmpdir + "/junc2", fake_msys_root + "/i.dont.exist"); | 
|  | ASSERT_FALSE(PathExists(JoinPath(fake_msys_root, "i.dont.exist"))); | 
|  | ASSERT_FALSE(PathExists(JoinPath(tmpdir, "junc2"))); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestIsDirectory) { | 
|  | ASSERT_FALSE(IsDirectory("")); | 
|  | ASSERT_FALSE(IsDirectory("/dev/null")); | 
|  | ASSERT_FALSE(IsDirectory("Nul")); | 
|  |  | 
|  | string tmpdir; | 
|  | GET_TEST_TMPDIR(tmpdir); | 
|  | ASSERT_TRUE(IsDirectory(tmpdir)); | 
|  | ASSERT_TRUE(IsDirectory("C:\\")); | 
|  | ASSERT_TRUE(IsDirectory("C:/")); | 
|  |  | 
|  | ASSERT_FALSE(IsDirectory("non.existent")); | 
|  | // Create a directory under `tempdir`, verify that IsDirectory reports true. | 
|  | string dir1(JoinPath(tmpdir, "dir1")); | 
|  | ASSERT_EQ(0, mkdir(dir1.c_str())); | 
|  | ASSERT_TRUE(IsDirectory(dir1)); | 
|  |  | 
|  | wstring wtmpdir(GetTestTmpDirW()); | 
|  | EXPECT_TRUE(CreateDummyFile(wtmpdir + L"\\dummy.txt")); | 
|  | ASSERT_FALSE(IsDirectory(tmpdir + "\\dummy.txt")); | 
|  |  | 
|  | // Verify that IsDirectory works for a junction. | 
|  | string junc1(JoinPath(tmpdir, "junc1")); | 
|  | CREATE_JUNCTION(junc1, dir1); | 
|  | ASSERT_TRUE(IsDirectory(junc1)); | 
|  |  | 
|  | ASSERT_EQ(0, rmdir(dir1.c_str())); | 
|  | ASSERT_FALSE(IsDirectory(dir1)); | 
|  | ASSERT_FALSE(IsDirectory(junc1)); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestUnlinkPath) { | 
|  | ASSERT_FALSE(UnlinkPath("/dev/null")); | 
|  | ASSERT_FALSE(UnlinkPath("Nul")); | 
|  |  | 
|  | string tmpdir; | 
|  | GET_TEST_TMPDIR(tmpdir); | 
|  |  | 
|  | // Create a directory under `tempdir`, a file inside it, and a junction | 
|  | // pointing to it. | 
|  | string dir1(JoinPath(tmpdir, "dir1")); | 
|  | ASSERT_EQ(0, mkdir(dir1.c_str())); | 
|  | AutoFileStream fh(fopen(JoinPath(dir1, "foo.txt").c_str(), "wt")); | 
|  | EXPECT_TRUE(fh.IsOpen()); | 
|  | ASSERT_LT(0, fprintf(fh, "hello\n")); | 
|  | fh.Close(); | 
|  | string junc1(JoinPath(tmpdir, "junc1")); | 
|  | CREATE_JUNCTION(junc1, dir1); | 
|  | ASSERT_TRUE(PathExists(junc1)); | 
|  | ASSERT_TRUE(PathExists(JoinPath(junc1, "foo.txt"))); | 
|  |  | 
|  | // Non-existent files cannot be unlinked. | 
|  | ASSERT_FALSE(UnlinkPath("does.not.exist")); | 
|  | // Directories cannot be unlinked. | 
|  | ASSERT_FALSE(UnlinkPath(dir1)); | 
|  | // Junctions can be unlinked, even if the pointed directory is not empty. | 
|  | ASSERT_TRUE(UnlinkPath(JoinPath(junc1, "foo.txt"))); | 
|  | // Files can be unlinked. | 
|  | ASSERT_TRUE(UnlinkPath(junc1)); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestMakeDirectories) { | 
|  | string tmpdir; | 
|  | GET_TEST_TMPDIR(tmpdir); | 
|  | ASSERT_LT(0, tmpdir.size()); | 
|  |  | 
|  | // Test that we can create come directories, can't create others. | 
|  | ASSERT_FALSE(MakeDirectories("", 0777)); | 
|  | ASSERT_FALSE(MakeDirectories("/dev/null", 0777)); | 
|  | ASSERT_TRUE(MakeDirectories("c:/", 0777)); | 
|  | ASSERT_TRUE(MakeDirectories("c:\\", 0777)); | 
|  | ASSERT_TRUE(MakeDirectories(".", 0777)); | 
|  | ASSERT_TRUE(MakeDirectories(tmpdir, 0777)); | 
|  | ASSERT_TRUE(MakeDirectories(JoinPath(tmpdir, "dir1/dir2/dir3"), 0777)); | 
|  |  | 
|  | string winpath = tmpdir + "\\dir4\\dir5"; | 
|  | std::replace(winpath.begin(), winpath.end(), '/', '\\'); | 
|  | ASSERT_TRUE(MakeDirectories(string("\\\\?\\") + winpath, 0777)); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestCanAccess) { | 
|  | ASSERT_FALSE(CanReadFile("C:/windows/this/should/not/exist/mkay")); | 
|  | ASSERT_FALSE(CanExecuteFile("C:/this/should/not/exist/mkay")); | 
|  | ASSERT_FALSE(CanAccessDirectory("C:/this/should/not/exist/mkay")); | 
|  |  | 
|  | ASSERT_FALSE(CanReadFile("non.existent")); | 
|  | ASSERT_FALSE(CanExecuteFile("non.existent")); | 
|  | ASSERT_FALSE(CanAccessDirectory("non.existent")); | 
|  |  | 
|  | string tmpdir; | 
|  | GET_TEST_TMPDIR(tmpdir); | 
|  | string dir(JoinPath(tmpdir, "canaccesstest")); | 
|  | ASSERT_EQ(0, mkdir(dir.c_str())); | 
|  |  | 
|  | ASSERT_FALSE(CanReadFile(dir)); | 
|  | ASSERT_FALSE(CanExecuteFile(dir)); | 
|  | ASSERT_TRUE(CanAccessDirectory(dir)); | 
|  |  | 
|  | string junc(JoinPath(tmpdir, "junc1")); | 
|  | CREATE_JUNCTION(junc, dir); | 
|  | ASSERT_FALSE(CanReadFile(junc)); | 
|  | ASSERT_FALSE(CanExecuteFile(junc)); | 
|  | ASSERT_TRUE(CanAccessDirectory(junc)); | 
|  |  | 
|  | string file(JoinPath(dir, "foo.txt")); | 
|  | AutoFileStream fh(fopen(file.c_str(), "wt")); | 
|  | EXPECT_TRUE(fh.IsOpen()); | 
|  | ASSERT_LT(0, fprintf(fh, "hello")); | 
|  | fh.Close(); | 
|  |  | 
|  | ASSERT_TRUE(CanReadFile(file)); | 
|  | ASSERT_FALSE(CanExecuteFile(file)); | 
|  | ASSERT_FALSE(CanAccessDirectory(file)); | 
|  |  | 
|  | ASSERT_TRUE(CanReadFile(JoinPath(junc, "foo.txt"))); | 
|  | ASSERT_FALSE(CanExecuteFile(JoinPath(junc, "foo.txt"))); | 
|  | ASSERT_FALSE(CanAccessDirectory(JoinPath(junc, "foo.txt"))); | 
|  |  | 
|  | string file2(JoinPath(dir, "foo.exe")); | 
|  | ASSERT_EQ(0, rename(file.c_str(), file2.c_str())); | 
|  | ASSERT_TRUE(CanReadFile(file2)); | 
|  | ASSERT_TRUE(CanExecuteFile(file2)); | 
|  | ASSERT_FALSE(CanAccessDirectory(file2)); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestMakeCanonical) { | 
|  | string tmpdir; | 
|  | GET_TEST_TMPDIR(tmpdir); | 
|  | // Create some scratch directories: $TEST_TMPDIR/directory/subdirectory | 
|  | string dir1(JoinPath(tmpdir, "directory")); | 
|  | string dir2(JoinPath(dir1, "subdirectory")); | 
|  | EXPECT_TRUE(MakeDirectories(dir2, 0700)); | 
|  | // Create a dummy file: $TEST_TMPDIR/directory/subdirectory/foo.txt | 
|  | string foo(JoinPath(dir2, "foo.txt")); | 
|  | wstring wfoo; | 
|  | EXPECT_TRUE(AsAbsoluteWindowsPath(foo, &wfoo, nullptr)); | 
|  | EXPECT_TRUE(CreateDummyFile(wfoo)); | 
|  | EXPECT_TRUE(CanReadFile(foo)); | 
|  | // Create junctions next to directory and subdirectory, pointing to them. | 
|  | // Use short paths and mixed casing to test that the canonicalization can | 
|  | // resolve these. | 
|  | //   $TEST_TMPDIR/junc12345 -> $TEST_TMPDIR/DIRECT~1 | 
|  | //   $TEST_TMPDIR/junc12~1/junc67890 -> $TEST_TMPDIR/JUNC12~1/SubDir~1 | 
|  | string sym1(JoinPath(tmpdir, "junc12345")); | 
|  | string sym2(JoinPath(JoinPath(tmpdir, "junc12~1"), "junc67890")); | 
|  | string sym1value(JoinPath(tmpdir, "DIRECT~1")); | 
|  | string sym2value(JoinPath(JoinPath(tmpdir, "JUNC12~1"), "SubDir~1")); | 
|  | CREATE_JUNCTION(sym1, sym1value); | 
|  | CREATE_JUNCTION(sym2, sym2value); | 
|  | // Expect that $TEST_TMPDIR/sym1/sym2/foo.txt is readable. | 
|  | string symfoo(JoinPath(sym2, "foo.txt")); | 
|  | EXPECT_TRUE(CanReadFile(symfoo)); | 
|  | // Assert the canonical path of foo.txt via the real path and via sym2. | 
|  | // The latter contains at least two junction components, shortened paths, and | 
|  | // mixed casing. | 
|  | string dircanon(MakeCanonical(foo.c_str())); | 
|  | string symcanon(MakeCanonical(symfoo.c_str())); | 
|  | string expected("directory\\subdirectory\\foo.txt"); | 
|  | ASSERT_NE(symcanon, ""); | 
|  | ASSERT_EQ(symcanon.find(expected), symcanon.size() - expected.size()); | 
|  | ASSERT_EQ(dircanon, symcanon); | 
|  | // Assert the canonical path of "subdirectory" via the real path and via sym2. | 
|  | // The latter contains at least two junction components, shortened paths, and | 
|  | // mixed casing. | 
|  | dircanon = MakeCanonical(dir2.c_str()); | 
|  | symcanon = MakeCanonical(sym2.c_str()); | 
|  | expected = "directory\\subdirectory"; | 
|  | ASSERT_NE(symcanon, ""); | 
|  | ASSERT_EQ(symcanon.find(expected), symcanon.size() - expected.size()); | 
|  | ASSERT_EQ(dircanon, symcanon); | 
|  | } | 
|  |  | 
|  | TEST_F(FileWindowsTest, TestMtimeHandling) { | 
|  | const char* tempdir_cstr = getenv("TEST_TMPDIR"); | 
|  | ASSERT_NE(tempdir_cstr, nullptr); | 
|  | ASSERT_NE(tempdir_cstr[0], 0); | 
|  | Path tempdir(tempdir_cstr); | 
|  |  | 
|  | Path target = tempdir.GetRelative("target" TOSTRING(__LINE__)); | 
|  | EXPECT_TRUE(CreateDirectoryW(target.AsNativePath().c_str(), nullptr)); | 
|  |  | 
|  | std::unique_ptr<IFileMtime> mtime(CreateFileMtime()); | 
|  | // Assert that a directory is always a good embedded binary. (We do not care | 
|  | // about directories' mtimes.) | 
|  | ASSERT_TRUE(mtime.get()->IsUntampered(target)); | 
|  | // Assert that junctions whose target exists are "good" embedded binaries. | 
|  | Path sym = tempdir.GetRelative("junc" TOSTRING(__LINE__)); | 
|  | EXPECT_EQ(CreateJunction(sym.AsNativePath(), target.AsNativePath(), nullptr), | 
|  | CreateJunctionResult::kSuccess); | 
|  | ASSERT_TRUE(mtime.get()->IsUntampered(sym)); | 
|  | // Assert that checking fails for non-existent directories and dangling | 
|  | // junctions. | 
|  | EXPECT_TRUE(RemoveDirectoryW(target.AsNativePath().c_str())); | 
|  | ASSERT_FALSE(mtime.get()->IsUntampered(target)); | 
|  | ASSERT_FALSE(mtime.get()->IsUntampered(sym)); | 
|  | } | 
|  |  | 
|  | }  // namespace blaze_util |