Windows, JNI: move around sources

Move the Windows JNI C++ sources to a separate
package and separate namespace.

This no-op refactoring allows other build rules
than Bazel's client library to depend on file I/O
and/or JNI functionality.

A follow-up commit will split the
//src/main/native/windows:processes library into
:jni-processes and :jni-file.

Change-Id: I33c5f8ebd8961cc440db3b4a95ff78024d7c1d74
PiperOrigin-RevId: 160404298
diff --git a/src/test/native/windows/file_test.cc b/src/test/native/windows/file_test.cc
new file mode 100644
index 0000000..043d4df
--- /dev/null
+++ b/src/test/native/windows/file_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2017 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.
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+
+#include <memory>  // unique_ptr
+#include <sstream>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "src/main/native/windows/file.h"
+#include "src/test/cpp/util/windows_test_util.h"
+
+#if !defined(COMPILER_MSVC) && !defined(__CYGWIN__)
+#error("This test should only be run on Windows")
+#endif  // !defined(COMPILER_MSVC) && !defined(__CYGWIN__)
+
+namespace bazel {
+namespace windows {
+
+using blaze_util::DeleteAllUnder;
+using blaze_util::GetTestTmpDirW;
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+static const wstring kUncPrefix = wstring(L"\\\\?\\");
+
+class WindowsFileOperationsTest : public ::testing::Test {
+ public:
+  void TearDown() override { DeleteAllUnder(GetTestTmpDirW()); }
+};
+
+TEST_F(WindowsFileOperationsTest, TestCreateJunction) {
+  wstring tmp(kUncPrefix + GetTestTmpDirW());
+  wstring target(tmp + L"\\junc_target");
+  EXPECT_TRUE(::CreateDirectoryW(target.c_str(), NULL));
+  wstring file1(target + L"\\foo");
+  EXPECT_TRUE(blaze_util::CreateDummyFile(file1));
+
+  EXPECT_EQ(IS_JUNCTION_NO, IsJunctionOrDirectorySymlink(target.c_str()));
+  EXPECT_NE(INVALID_FILE_ATTRIBUTES, ::GetFileAttributesW(file1.c_str()));
+
+  wstring name(tmp + L"\\junc_name");
+
+  // Create junctions from all combinations of UNC-prefixed or non-prefixed name
+  // and target paths.
+  ASSERT_EQ("", CreateJunction(name + L"1", target));
+  ASSERT_EQ("", CreateJunction(name + L"2", target.substr(4)));
+  ASSERT_EQ("", CreateJunction(name.substr(4) + L"3", target));
+  ASSERT_EQ("", CreateJunction(name.substr(4) + L"4", target.substr(4)));
+
+  // Assert creation of the junctions.
+  ASSERT_EQ(IS_JUNCTION_YES,
+            IsJunctionOrDirectorySymlink((name + L"1").c_str()));
+  ASSERT_EQ(IS_JUNCTION_YES,
+            IsJunctionOrDirectorySymlink((name + L"2").c_str()));
+  ASSERT_EQ(IS_JUNCTION_YES,
+            IsJunctionOrDirectorySymlink((name + L"3").c_str()));
+  ASSERT_EQ(IS_JUNCTION_YES,
+            IsJunctionOrDirectorySymlink((name + L"4").c_str()));
+
+  // Assert that the file is visible under all junctions.
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"1\\foo").c_str()));
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"2\\foo").c_str()));
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"3\\foo").c_str()));
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"4\\foo").c_str()));
+
+  // Assert that no other file exists under the junctions.
+  wstring file2(target + L"\\bar");
+  ASSERT_EQ(INVALID_FILE_ATTRIBUTES, ::GetFileAttributesW(file2.c_str()));
+  ASSERT_EQ(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"1\\bar").c_str()));
+  ASSERT_EQ(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"2\\bar").c_str()));
+  ASSERT_EQ(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"3\\bar").c_str()));
+  ASSERT_EQ(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"4\\bar").c_str()));
+
+  // Create a new file.
+  EXPECT_TRUE(blaze_util::CreateDummyFile(file2));
+  EXPECT_NE(INVALID_FILE_ATTRIBUTES, ::GetFileAttributesW(file2.c_str()));
+
+  // Assert that the newly created file appears under all junctions.
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"1\\bar").c_str()));
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"2\\bar").c_str()));
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"3\\bar").c_str()));
+  ASSERT_NE(INVALID_FILE_ATTRIBUTES,
+            ::GetFileAttributesW((name + L"4\\bar").c_str()));
+}
+
+}  // namespace windows
+}  // namespace bazel
diff --git a/src/test/native/windows/util_test.cc b/src/test/native/windows/util_test.cc
new file mode 100644
index 0000000..8cbb15a
--- /dev/null
+++ b/src/test/native/windows/util_test.cc
@@ -0,0 +1,368 @@
+// Copyright 2017 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.
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+
+#include <algorithm>   // replace
+#include <functional>  // function
+#include <memory>      // unique_ptr
+#include <string>
+
+#include "gtest/gtest.h"
+#include "src/main/native/windows/util.h"
+
+#if !defined(COMPILER_MSVC) && !defined(__CYGWIN__)
+#error("This test should only be run on Windows")
+#endif  // !defined(COMPILER_MSVC) && !defined(__CYGWIN__)
+
+namespace bazel {
+namespace windows {
+
+using std::function;
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+static const wstring kUncPrefix = wstring(L"\\\\?\\");
+
+// Retrieves TEST_TMPDIR as a shortened path. Result won't have a "\\?\" prefix.
+static void GetShortTempDir(wstring* result) {
+  unique_ptr<WCHAR[]> buf;
+  DWORD size = ::GetEnvironmentVariableW(L"TEST_TMPDIR", NULL, 0);
+  ASSERT_GT(size, (DWORD)0);
+  // `size` accounts for the null-terminator
+  buf.reset(new WCHAR[size]);
+  ::GetEnvironmentVariableW(L"TEST_TMPDIR", buf.get(), size);
+
+  // Add "\\?\" prefix.
+  wstring tmpdir = kUncPrefix + wstring(buf.get());
+
+  // Remove trailing '/' or '\' and replace all '/' with '\'.
+  if (tmpdir.back() == '/' || tmpdir.back() == '\\') {
+    tmpdir.pop_back();
+  }
+  std::replace(tmpdir.begin(), tmpdir.end(), '/', '\\');
+
+  // Convert to 8dot3 style short path.
+  size = ::GetShortPathNameW(tmpdir.c_str(), NULL, 0);
+  ASSERT_GT(size, (DWORD)0);
+  // `size` accounts for the null-terminator
+  buf.reset(new WCHAR[size]);
+  ::GetShortPathNameW(tmpdir.c_str(), buf.get(), size);
+
+  // Set the result, omit the "\\?\" prefix.
+  // Ensure that the result is shorter than MAX_PATH and also has room for a
+  // backslash (1 char) and a single-letter executable name with .bat
+  // extension (5 chars).
+  *result = wstring(buf.get() + 4);
+  ASSERT_LT(result->size(), MAX_PATH - 6);
+}
+
+// If `success` is true, returns an empty string, otherwise an error message.
+// The error message will have the format "Failed: operation(arg)" using the
+// specified `operation` and `arg` strings.
+static wstring ReturnEmptyOrError(bool success, const wstring& operation,
+                                  const wstring& arg) {
+  return success ? L"" : (wstring(L"Failed: ") + operation + L"(" + arg + L")");
+}
+
+// Creates a dummy file under `path`. `path` should NOT have a "\\?\" prefix.
+static wstring CreateDummyFile(wstring path) {
+  path = kUncPrefix + path;
+  HANDLE handle = ::CreateFileW(
+      /* lpFileName */ path.c_str(),
+      /* dwDesiredAccess */ GENERIC_WRITE,
+      /* dwShareMode */ FILE_SHARE_READ,
+      /* lpSecurityAttributes */ NULL,
+      /* dwCreationDisposition */ CREATE_ALWAYS,
+      /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
+      /* hTemplateFile */ NULL);
+  if (handle == INVALID_HANDLE_VALUE) {
+    return ReturnEmptyOrError(false, L"CreateFileW", path);
+  }
+  DWORD actually_written = 0;
+  WriteFile(handle, "hello", 5, &actually_written, NULL);
+  if (actually_written == 0) {
+    return ReturnEmptyOrError(false, L"WriteFile", path);
+  }
+  CloseHandle(handle);
+  return L"";
+}
+
+// Asserts that a dummy file under `path` can be created.
+// This is a macro so the assertions will have the correct line number.
+#define CREATE_FILE(/* const wstring& */ path) \
+  { ASSERT_EQ(CreateDummyFile(path), L""); }
+
+// Deletes a file under `path`. `path` should NOT have a "\\?\" prefix.
+static wstring DeleteDummyFile(wstring path) {
+  path = kUncPrefix + path;
+  return ReturnEmptyOrError(::DeleteFileW(path.c_str()), L"DeleteFileW", path);
+}
+
+// Asserts that a file under `path` can be deleted.
+// This is a macro so the assertions will have the correct line number.
+#define DELETE_FILE(/* const wstring& */ path) \
+  { ASSERT_EQ(DeleteDummyFile(path), L""); }
+
+// Creates a directory under `path`. `path` should NOT have a "\\?\" prefix.
+static wstring CreateDir(wstring path) {
+  path = kUncPrefix + path;
+  return ReturnEmptyOrError(::CreateDirectoryW(path.c_str(), NULL),
+                            L"CreateDirectoryW", path);
+}
+
+// Asserts that a directory under `path` can be created.
+// This is a macro so the assertions will have the correct line number.
+#define CREATE_DIR(/* const wstring& */ path) \
+  { ASSERT_EQ(CreateDir(path), L""); }
+
+// Deletes an empty directory under `path`.
+// `path` should NOT have a "\\?\" prefix.
+static wstring DeleteDir(wstring path) {
+  path = kUncPrefix + path;
+  return ReturnEmptyOrError(::RemoveDirectoryW(path.c_str()),
+                            L"RemoveDirectoryW", path);
+}
+
+// Asserts that the empty directory under `path` can be deleted.
+// This is a macro so the assertions will have the correct line number.
+#define DELETE_DIR(/* const wstring& */ path) \
+  { ASSERT_EQ(DeleteDir(path), L""); }
+
+// Appends a file name segment with ".bat" extension to `result`.
+// `length` specifies how long the segment may be, and it includes the "\" at
+// the beginning. `length` must be in [6..13], so the shortest segment is
+// "\a.bat", the longest is "\abcdefgh.bat".
+// For example APPEND_FILE_SEGMENT(8, result) will append "\abc.bat" to
+// `result`.
+// This is a macro so the assertions will have the correct line number.
+#define APPEND_FILE_SEGMENT(/* size_t */ length, /* wstring* */ result) \
+  {                                                                     \
+    ASSERT_GE(length, 6);                                               \
+    ASSERT_LE(length, 13);                                              \
+    *result += wstring(L"\\abcdefgh", length - 4) + L".bat";            \
+  }
+
+// Creates subdirectories under `basedir` and sets `result_path` to the deepest.
+//
+// `basedir` should be a shortened path, without "\\?\" prefix.
+// `result_path` will be also a short path under `basedir`.
+//
+// Every directory in `result_path` will be created. The entire length of this
+// path will be exactly MAX_PATH - 7 (not including null-terminator).
+// Just by appending a file name segment between 6 and 8 characters long (i.e.
+// "\a.bat", "\ab.bat", or "\abc.bat") the caller can obtain a path that is
+// MAX_PATH - 1 long, or MAX_PATH long, or MAX_PATH + 1 long, respectively,
+// and cannot be shortened further.
+static void CreateShortDirsUnder(wstring basedir, wstring* result_path) {
+  ASSERT_LT(basedir.size(), MAX_PATH);
+  size_t remaining_len = MAX_PATH - 1 - basedir.size();
+  ASSERT_GE(remaining_len, 6);  // has room for suffix "\a.bat"
+
+  // If `remaining_len` is odd, make it even.
+  if (remaining_len % 2) {
+    remaining_len -= 3;
+    basedir += wstring(L"\\ab");
+    CREATE_DIR(basedir);
+  }
+
+  // Keep adding 2 chars long segments until we only have 6 chars left.
+  while (remaining_len >= 8) {
+    remaining_len -= 2;
+    basedir += wstring(L"\\a");
+    CREATE_DIR(basedir);
+  }
+  ASSERT_EQ(basedir.size(), MAX_PATH - 1 - 6);
+  *result_path = basedir;
+}
+
+// Deletes `deepest_subdir` and all of its parents below `basedir`.
+// `basedir` must be a prefix (ancestor) of `deepest_subdir`. Neither of them
+// should have a "\\?\" prefix.
+// Every subdirectory connecting `basedir` and `deepest_subdir` must be empty
+// except for the single directory child connecting these two nodes. In other
+// words it should be possible to remove all directories starting at
+// `deepest_subdir` and walking up the tree until `basedir` is reached.
+// `basedir` is NOT deleted and it doesn't need to be empty either.
+static void DeleteDirsUnder(const wstring& basedir,
+                            const wstring& deepest_subdir) {
+  // Assert that `deepest_subdir` starts with `basedir`.
+  ASSERT_EQ(deepest_subdir.find(basedir), 0);
+
+  // Make a mutable copy of `deepest_subdir`.
+  unique_ptr<WCHAR[]> mutable_path(new WCHAR[deepest_subdir.size() + 1]);
+  memcpy(mutable_path.get(), deepest_subdir.c_str(),
+         deepest_subdir.size() * sizeof(wchar_t));
+  mutable_path.get()[deepest_subdir.size()] = 0;
+
+  // Mark the end of the path. We'll keep setting the last directory separator
+  // to the null-terminator, thus walking up the directory tree.
+  wchar_t* p_end = mutable_path.get() + deepest_subdir.size();
+
+  while (p_end > mutable_path.get() + basedir.size()) {
+    DELETE_DIR(mutable_path.get());
+    // Walk up one level in the path.
+    while (*p_end != '\\') {
+      --p_end;
+    }
+    *p_end = '\0';
+  }
+}
+
+// Converts a wstring to a string using `wcstombs`.
+static string AsString(const wstring& s) {
+  size_t size = wcstombs(nullptr, s.c_str(), 0) + 1;
+  unique_ptr<char[]> result(new char[size]);
+  wcstombs(result.get(), s.c_str(), size);
+  return string(result.get());
+}
+
+// Converts a string to a wstring using `mbstowcs`.
+static wstring AsWstring(const char* s) {
+  size_t size = mbstowcs(nullptr, s, 0) + 1;
+  unique_ptr<WCHAR[]> result(new WCHAR[size]);
+  mbstowcs(result.get(), s, size);
+  return wstring(result.get());
+}
+
+static function<wstring()> MakeConversionFunc(const char* input) {
+  return [input]() { return AsWstring(input); };
+}
+
+// Asserts that `str` contains substring `substr`.
+// This is a macro so the assertions will have the correct line number.
+#define ASSERT_CONTAINS(/* const string& */ str, /* const char* */ substr) \
+  {                                                                        \
+    ASSERT_NE(str, "");                                                    \
+    ASSERT_NE(str.find(substr), string::npos);                             \
+  }
+
+// This is a macro so the assertions will have the correct line number.
+#define ASSERT_SHORTENING_FAILS(/* const char* */ input,            \
+                                /* const char* */ error_msg)        \
+  {                                                                 \
+    string actual;                                                  \
+    ASSERT_CONTAINS(AsExecutablePathForCreateProcess(               \
+                        input, MakeConversionFunc(input), &actual), \
+                    error_msg);                                     \
+  }
+
+// This is a macro so the assertions will have the correct line number.
+#define ASSERT_SHORTENING_SUCCEEDS(/* const char* */ input,             \
+                                   /* const string& */ expected_result) \
+  {                                                                     \
+    string actual;                                                      \
+    ASSERT_EQ(AsExecutablePathForCreateProcess(                         \
+                  input, MakeConversionFunc(input), &actual),           \
+              "");                                                      \
+    ASSERT_EQ(actual, expected_result);                                 \
+  }
+
+TEST(WindowsUtilTest, TestAsExecutablePathForCreateProcessBadInputs) {
+  ASSERT_SHORTENING_FAILS("", "should not be empty");
+  ASSERT_SHORTENING_FAILS("\"cmd.exe\"", "should not be quoted");
+  ASSERT_SHORTENING_FAILS("/dev/null", "path='/dev/null' is absolute");
+  ASSERT_SHORTENING_FAILS("/usr/bin/bash", "path='/usr/bin/bash' is absolute");
+  ASSERT_SHORTENING_FAILS("foo\\bar.exe", "absolute");
+  ASSERT_SHORTENING_FAILS("foo\\..\\bar.exe", "normalized");
+  ASSERT_SHORTENING_FAILS("\\bar.exe", "path='\\bar.exe' is absolute");
+
+  string dummy = "hello";
+  while (dummy.size() < MAX_PATH) {
+    dummy += dummy;
+  }
+  dummy += ".exe";
+  ASSERT_SHORTENING_FAILS(dummy.c_str(), "a file name but too long");
+}
+
+TEST(WindowsUtilTest, TestAsExecutablePathForCreateProcessConversions) {
+  wstring tmpdir;
+  GetShortTempDir(&tmpdir);
+  wstring short_root;
+  CreateShortDirsUnder(tmpdir, &short_root);
+
+  // Assert that we have enough room to append a file name that is just short
+  // enough to fit into MAX_PATH - 1, or one that's just long enough to make
+  // the whole path MAX_PATH long or longer.
+  ASSERT_EQ(short_root.size(), MAX_PATH - 1 - 6);
+
+  string actual;
+  string error;
+  for (size_t i = 0; i < 3; ++i) {
+    wstring wfilename = short_root;
+
+    APPEND_FILE_SEGMENT(6 + i, &wfilename);
+    string filename = AsString(wfilename);
+    ASSERT_EQ(filename.size(), MAX_PATH - 1 + i);
+
+    // When i=0 then `filename` is MAX_PATH - 1 long, so
+    // `AsExecutablePathForCreateProcess` will not attempt to shorten it, and
+    // so it also won't notice that the file doesn't exist. If however we pass
+    // a non-existent path to CreateProcessA, then it'll fail, so we'll find out
+    // about this error in production code.
+    // When i>0 then `filename` is at least MAX_PATH long, so
+    // `AsExecutablePathForCreateProcess` will attempt to shorten it, but
+    // because the file doesn't yet exist, the shortening attempt will fail.
+    if (i > 0) {
+      ASSERT_EQ(::GetFileAttributesA(filename.c_str()),
+                INVALID_FILE_ATTRIBUTES);
+      ASSERT_SHORTENING_FAILS(filename.c_str(), "GetShortPathName failed");
+    }
+
+    // Create the file, now we should be able to shorten it when i=0, but not
+    // otherwise.
+    CREATE_FILE(wfilename);
+    if (i == 0) {
+      // The filename was short enough.
+      ASSERT_SHORTENING_SUCCEEDS(filename.c_str(),
+                                 string("\"") + filename + "\"");
+    } else {
+      // The filename was too long to begin with, and it was impossible to
+      // shorten any of the segments (since we deliberately created them that
+      // way), so shortening failed.
+      ASSERT_SHORTENING_FAILS(filename.c_str(), "would not shorten");
+    }
+    DELETE_FILE(wfilename);
+  }
+
+  // Finally construct a path that can and will be shortened. Just walk up a few
+  // levels in `short_root` and create a long file name that can be shortened.
+  wstring wshortenable_root = short_root;
+  while (wshortenable_root.size() > MAX_PATH - 1 - 13) {
+    wshortenable_root =
+        wshortenable_root.substr(0, wshortenable_root.find_last_of('\\'));
+  }
+  wstring wshortenable = wshortenable_root + wstring(L"\\") +
+                         wstring(MAX_PATH - wshortenable_root.size(), 'a') +
+                         wstring(L".bat");
+  ASSERT_GT(wshortenable.size(), MAX_PATH);
+
+  // Attempt to shorten. It will fail because the file doesn't exist yet.
+  ASSERT_SHORTENING_FAILS(AsString(wshortenable).c_str(),
+                          "GetShortPathName failed");
+
+  // Create the file so shortening will succeed.
+  CREATE_FILE(wshortenable);
+  ASSERT_SHORTENING_SUCCEEDS(
+      AsString(wshortenable).c_str(),
+      string("\"") + AsString(wshortenable_root) + "\\AAAAAA~1.BAT\"");
+  DELETE_FILE(wshortenable);
+
+  DeleteDirsUnder(tmpdir, short_root);
+}
+
+}  // namespace windows
+}  // namespace bazel