瀏覽代碼

Merge pull request #2969 from laszlocsomor/master

Windows: support long paths in open/mkdir/access
Feng Xiao 8 年之前
父節點
當前提交
f15185d370

+ 14 - 0
BUILD

@@ -108,6 +108,7 @@ cc_library(
         "src/google/protobuf/stubs/bytestream.cc",
         "src/google/protobuf/stubs/common.cc",
         "src/google/protobuf/stubs/int128.cc",
+        "src/google/protobuf/stubs/io_win32.cc",
         "src/google/protobuf/stubs/once.cc",
         "src/google/protobuf/stubs/status.cc",
         "src/google/protobuf/stubs/statusor.cc",
@@ -160,6 +161,7 @@ cc_library(
         "src/google/protobuf/service.cc",
         "src/google/protobuf/source_context.pb.cc",
         "src/google/protobuf/struct.pb.cc",
+        "src/google/protobuf/stubs/io_win32.cc",
         "src/google/protobuf/stubs/mathlimits.cc",
         "src/google/protobuf/stubs/substitute.cc",
         "src/google/protobuf/text_format.cc",
@@ -471,6 +473,7 @@ COMMON_TEST_SRCS = [
     # AUTOGEN(common_test_srcs)
     "src/google/protobuf/arena_test_util.cc",
     "src/google/protobuf/map_test_util.cc",
+    "src/google/protobuf/stubs/io_win32.cc",
     "src/google/protobuf/test_util.cc",
     "src/google/protobuf/testing/file.cc",
     "src/google/protobuf/testing/googletest.cc",
@@ -491,6 +494,16 @@ cc_binary(
     ],
 )
 
+cc_test(
+    name = "win32_test",
+    srcs = ["src/google/protobuf/stubs/io_win32_unittest.cc"],
+    deps = [
+        ":protobuf_lite",
+        "//external:gtest_main",
+    ],
+    tags = ["manual", "windows"],
+)
+
 cc_test(
     name = "protobuf_test",
     srcs = COMMON_TEST_SRCS + [
@@ -537,6 +550,7 @@ cc_test(
         "src/google/protobuf/stubs/bytestream_unittest.cc",
         "src/google/protobuf/stubs/common_unittest.cc",
         "src/google/protobuf/stubs/int128_unittest.cc",
+        "src/google/protobuf/stubs/io_win32_unittest.cc",
         "src/google/protobuf/stubs/once_unittest.cc",
         "src/google/protobuf/stubs/status_test.cc",
         "src/google/protobuf/stubs/statusor_test.cc",

+ 1 - 0
cmake/extract_includes.bat.in

@@ -100,6 +100,7 @@ copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\casts.h" includ
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\common.h" include\google\protobuf\stubs\common.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\fastmem.h" include\google\protobuf\stubs\fastmem.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\hash.h" include\google\protobuf\stubs\hash.h
+copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\io_win32.h" include\google\protobuf\stubs\io_win32.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\logging.h" include\google\protobuf\stubs\logging.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\macros.h" include\google\protobuf\stubs\macros.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\stubs\mutex.h" include\google\protobuf\stubs\mutex.h

+ 1 - 0
cmake/libprotobuf-lite.cmake

@@ -13,6 +13,7 @@ set(libprotobuf_lite_files
   ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/common.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/int128.cc
+  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/once.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/status.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc

+ 1 - 0
cmake/libprotoc.cmake

@@ -93,6 +93,7 @@ set(libprotoc_files
   ${protobuf_source_dir}/src/google/protobuf/compiler/ruby/ruby_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/subprocess.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/zip_writer.cc
+  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32.cc
 )
 
 set(libprotoc_headers

+ 3 - 0
cmake/tests.cmake

@@ -160,6 +160,8 @@ set(tests_files
   ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/common_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/int128_unittest.cc
+  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32.cc
+  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/once_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/status_test.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/statusor_test.cc
@@ -198,6 +200,7 @@ target_link_libraries(tests libprotoc libprotobuf gmock_main)
 
 set(test_plugin_files
   ${protobuf_source_dir}/src/google/protobuf/compiler/mock_code_generator.cc
+  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32.cc
   ${protobuf_source_dir}/src/google/protobuf/testing/file.cc
   ${protobuf_source_dir}/src/google/protobuf/testing/file.h
   ${protobuf_source_dir}/src/google/protobuf/compiler/test_plugin.cc

+ 3 - 0
src/Makefile.am

@@ -198,6 +198,8 @@ libprotobuf_lite_la_SOURCES =                                  \
   google/protobuf/stubs/hash.h                                 \
   google/protobuf/stubs/int128.cc                              \
   google/protobuf/stubs/int128.h                               \
+  google/protobuf/stubs/io_win32.cc                            \
+  google/protobuf/stubs/io_win32.h                             \
   google/protobuf/stubs/map_util.h                             \
   google/protobuf/stubs/mathutil.h                             \
   google/protobuf/stubs/once.cc                                \
@@ -762,6 +764,7 @@ protobuf_test_SOURCES =                                        \
   google/protobuf/stubs/bytestream_unittest.cc                 \
   google/protobuf/stubs/common_unittest.cc                     \
   google/protobuf/stubs/int128_unittest.cc                     \
+  google/protobuf/stubs/io_win32_unittest.cc                   \
   google/protobuf/stubs/once_unittest.cc                       \
   google/protobuf/stubs/statusor_test.cc                       \
   google/protobuf/stubs/status_test.cc                         \

+ 14 - 24
src/google/protobuf/compiler/command_line_interface.cc

@@ -45,10 +45,7 @@
 #endif
 #include <sys/stat.h>
 #include <fcntl.h>
-#ifdef _MSC_VER
-#include <io.h>
-#include <direct.h>
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
 #endif
 #include <errno.h>
@@ -80,6 +77,7 @@
 #include <google/protobuf/io/coded_stream.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <google/protobuf/io/printer.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/stubs/strutil.h>
 #include <google/protobuf/stubs/substitute.h>
@@ -91,22 +89,6 @@ namespace google {
 namespace protobuf {
 namespace compiler {
 
-#if defined(_WIN32)
-#define mkdir(name, mode) mkdir(name)
-#ifndef W_OK
-#define W_OK 02  // not defined by MSVC for whatever reason
-#endif
-#ifndef F_OK
-#define F_OK 00  // not defined by MSVC for whatever reason
-#endif
-#ifndef STDIN_FILENO
-#define STDIN_FILENO 0
-#endif
-#ifndef STDOUT_FILENO
-#define STDOUT_FILENO 1
-#endif
-#endif
-
 #ifndef O_BINARY
 #ifdef _O_BINARY
 #define O_BINARY _O_BINARY
@@ -118,6 +100,14 @@ namespace compiler {
 namespace {
 #if defined(_WIN32) && !defined(__CYGWIN__)
 static const char* kPathSeparator = ";";
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::access;
+using google::protobuf::internal::win32::close;
+using google::protobuf::internal::win32::mkdir;
+using google::protobuf::internal::win32::open;
+using google::protobuf::internal::win32::setmode;
+using google::protobuf::internal::win32::write;
 #else
 static const char* kPathSeparator = ":";
 #endif
@@ -141,9 +131,9 @@ static bool IsWindowsAbsolutePath(const string& text) {
 
 void SetFdToTextMode(int fd) {
 #ifdef _WIN32
-  if (_setmode(fd, _O_TEXT) == -1) {
+  if (setmode(fd, _O_TEXT) == -1) {
     // This should never happen, I think.
-    GOOGLE_LOG(WARNING) << "_setmode(" << fd << ", _O_TEXT): " << strerror(errno);
+    GOOGLE_LOG(WARNING) << "setmode(" << fd << ", _O_TEXT): " << strerror(errno);
   }
 #endif
   // (Text and binary are the same on non-Windows platforms.)
@@ -151,9 +141,9 @@ void SetFdToTextMode(int fd) {
 
 void SetFdToBinaryMode(int fd) {
 #ifdef _WIN32
-  if (_setmode(fd, _O_BINARY) == -1) {
+  if (setmode(fd, _O_BINARY) == -1) {
     // This should never happen, I think.
-    GOOGLE_LOG(WARNING) << "_setmode(" << fd << ", _O_BINARY): " << strerror(errno);
+    GOOGLE_LOG(WARNING) << "setmode(" << fd << ", _O_BINARY): " << strerror(errno);
   }
 #endif
   // (Text and binary are the same on non-Windows platforms.)

+ 13 - 15
src/google/protobuf/compiler/command_line_interface_unittest.cc

@@ -35,9 +35,7 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#ifdef _MSC_VER
-#include <io.h>
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
 #endif
 #include <memory>
@@ -65,27 +63,27 @@
 #include <gtest/gtest.h>
 
 #include <google/protobuf/stubs/strutil.h>
+#include <google/protobuf/stubs/io_win32.h>
 
 namespace google {
 namespace protobuf {
 namespace compiler {
 
+#if defined(_WIN32)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::access;
+using google::protobuf::internal::win32::dup;
+using google::protobuf::internal::win32::dup2;
+using google::protobuf::internal::win32::close;
+using google::protobuf::internal::win32::open;
+using google::protobuf::internal::win32::write;
+#endif
+
 // Disable the whole test when we use tcmalloc for "draconian" heap checks, in
 // which case tcmalloc will print warnings that fail the plugin tests.
 #if !GOOGLE_PROTOBUF_HEAP_CHECK_DRACONIAN
 
-#if defined(_WIN32)
-#ifndef STDIN_FILENO
-#define STDIN_FILENO 0
-#endif
-#ifndef STDOUT_FILENO
-#define STDOUT_FILENO 1
-#endif
-#ifndef F_OK
-#define F_OK 00  // not defined by MSVC for whatever reason
-#endif
-#endif
-
 namespace {
 
 bool FileExists(const string& path) {

+ 10 - 8
src/google/protobuf/compiler/importer.cc

@@ -33,7 +33,7 @@
 //  Sanjay Ghemawat, Jeff Dean, and others.
 
 #ifdef _MSC_VER
-#include <io.h>
+#include <direct.h>
 #else
 #include <unistd.h>
 #endif
@@ -53,19 +53,21 @@
 #include <google/protobuf/compiler/parser.h>
 #include <google/protobuf/io/tokenizer.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/strutil.h>
 
-namespace google {
-namespace protobuf {
-namespace compiler {
-
 #ifdef _WIN32
-#ifndef F_OK
-#define F_OK 00  // not defined by MSVC for whatever reason
-#endif
 #include <ctype.h>
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::access;
+using google::protobuf::internal::win32::open;
 #endif
 
+namespace google {
+namespace protobuf {
+namespace compiler {
+
 // Returns true if the text looks like a Windows-style absolute path, starting
 // with a drive letter.  Example:  "C:\foo".  TODO(kenton):  Share this with
 // copy in command_line_interface.cc?

+ 8 - 3
src/google/protobuf/compiler/objectivec/objectivec_helpers.cc

@@ -28,9 +28,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#ifdef _MSC_VER
-#include <io.h>
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
 #endif
 #include <climits>
@@ -49,8 +47,15 @@
 #include <google/protobuf/io/printer.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/strutil.h>
 
+#if defined(_WIN32)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::open;
+#endif
+
 // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
 // error cases, so it seems to be ok to use as a back door for errors.
 

+ 8 - 9
src/google/protobuf/compiler/plugin.cc

@@ -36,18 +36,12 @@
 #include <set>
 
 #ifdef _WIN32
-#include <io.h>
 #include <fcntl.h>
-#ifndef STDIN_FILENO
-#define STDIN_FILENO 0
-#endif
-#ifndef STDOUT_FILENO
-#define STDOUT_FILENO 1
-#endif
 #else
 #include <unistd.h>
 #endif
 
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/stubs/common.h>
 #include <google/protobuf/compiler/plugin.pb.h>
@@ -55,6 +49,11 @@
 #include <google/protobuf/descriptor.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 
+#if defined(_WIN32)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::setmode;
+#endif
 
 namespace google {
 namespace protobuf {
@@ -150,8 +149,8 @@ int PluginMain(int argc, char* argv[], const CodeGenerator* generator) {
   }
 
 #ifdef _WIN32
-  _setmode(STDIN_FILENO, _O_BINARY);
-  _setmode(STDOUT_FILENO, _O_BINARY);
+  setmode(STDIN_FILENO, _O_BINARY);
+  setmode(STDOUT_FILENO, _O_BINARY);
 #endif
 
   CodeGeneratorRequest request;

+ 9 - 4
src/google/protobuf/io/zero_copy_stream_impl.cc

@@ -32,9 +32,7 @@
 //  Based on original Protocol Buffers design by
 //  Sanjay Ghemawat, Jeff Dean, and others.
 
-#ifdef _MSC_VER
-#include <io.h>
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -43,9 +41,9 @@
 #include <errno.h>
 #include <iostream>
 #include <algorithm>
-
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/stubs/stl_util.h>
 
@@ -58,6 +56,13 @@ namespace io {
 // Win32 lseek is broken:  If invoked on a non-seekable file descriptor, its
 // return value is undefined.  We re-define it to always produce an error.
 #define lseek(fd, offset, origin) ((off_t)-1)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::access;
+using google::protobuf::internal::win32::close;
+using google::protobuf::internal::win32::open;
+using google::protobuf::internal::win32::read;
+using google::protobuf::internal::win32::write;
 #endif
 
 namespace {

+ 8 - 3
src/google/protobuf/io/zero_copy_stream_unittest.cc

@@ -47,9 +47,7 @@
 //   implementations.
 
 
-#ifdef _MSC_VER
-#include <io.h>
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
 #endif
 #include <stdlib.h>
@@ -72,6 +70,7 @@
 #endif
 
 #include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/testing/googletest.h>
 #include <google/protobuf/testing/file.h>
@@ -84,6 +83,12 @@ namespace {
 
 #ifdef _WIN32
 #define pipe(fds) _pipe(fds, 4096, O_BINARY)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::access;
+using google::protobuf::internal::win32::mkdir;
+using google::protobuf::internal::win32::open;
+using google::protobuf::internal::win32::close;
 #endif
 
 #ifndef O_BINARY

+ 9 - 3
src/google/protobuf/message_unittest.cc

@@ -37,9 +37,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#ifdef _MSC_VER
-#include <io.h>
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
 #endif
 #include <sstream>
@@ -56,6 +54,7 @@
 
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/testing/googletest.h>
 #include <gtest/gtest.h>
@@ -63,6 +62,13 @@
 namespace google {
 namespace protobuf {
 
+#if defined(_WIN32)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::close;
+using google::protobuf::internal::win32::open;
+#endif
+
 #ifndef O_BINARY
 #ifdef _O_BINARY
 #define O_BINARY _O_BINARY

+ 362 - 0
src/google/protobuf/stubs/io_win32.cc

@@ -0,0 +1,362 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: laszlocsomor@google.com (Laszlo Csomor)
+//
+// Implementation for long-path-aware open/mkdir/etc. on Windows.
+//
+// These functions convert the input path to an absolute Windows path
+// with "\\?\" prefix if necessary, then pass that to _wopen/_wmkdir/etc.
+// (declared in <io.h>) respectively. This allows working with files/directories
+// whose paths are longer than MAX_PATH (260 chars).
+//
+// This file is only used on Windows, it's empty on other platforms.
+
+#if defined(_WIN32)
+
+// Comment this out to fall back to using the ANSI versions (open, mkdir, ...)
+// instead of the Unicode ones (_wopen, _wmkdir, ...). Doing so can be useful to
+// debug failing tests if that's caused by the long path support.
+#define SUPPORT_LONGPATHS
+
+#include <ctype.h>
+#include <direct.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <wctype.h>
+#include <windows.h>
+
+#include <google/protobuf/stubs/io_win32.h>
+
+#include <cassert>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace google {
+namespace protobuf {
+namespace internal {
+namespace win32 {
+namespace {
+
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+template <typename char_type>
+struct CharTraits {
+  static bool is_alpha(char_type ch);
+};
+
+template <>
+struct CharTraits<char> {
+  static bool is_alpha(char ch) { return isalpha(ch); }
+};
+
+template <>
+struct CharTraits<wchar_t> {
+  static bool is_alpha(wchar_t ch) { return iswalpha(ch); }
+};
+
+// Returns true if the path starts with a drive letter, e.g. "c:".
+// Note that this won't check for the "\" after the drive letter, so this also
+// returns true for "c:foo" (which is "c:\${PWD}\foo").
+// This check requires that a path not have a longpath prefix ("\\?\").
+template <typename char_type>
+bool has_drive_letter(const char_type* ch) {
+  return CharTraits<char_type>::is_alpha(ch[0]) && ch[1] == ':';
+}
+
+// Returns true if the path starts with a longpath prefix ("\\?\").
+template <typename char_type>
+bool has_longpath_prefix(const char_type* path) {
+  return path[0] == '\\' && path[1] == '\\' && path[2] == '?' &&
+         path[3] == '\\';
+}
+
+// Returns true if the path starts with a drive specifier (e.g. "c:\").
+template <typename char_type>
+bool is_path_absolute(const char_type* path) {
+  return has_drive_letter(path) && is_separator(path[2]);
+}
+
+template <typename char_type>
+bool is_separator(char_type c) {
+  return c == '/' || c == '\\';
+}
+
+template <typename char_type>
+bool is_drive_relative(const char_type* path) {
+  return has_drive_letter(path) && (path[2] == 0 || !is_separator(path[2]));
+}
+
+template <typename char_type>
+void replace_directory_separators(char_type* p) {
+  for (; *p; ++p) {
+    if (*p == '/') {
+      *p = '\\';
+    }
+  }
+}
+
+string join_paths(const string& path1, const string& path2) {
+  if (path1.empty() || is_path_absolute(path2.c_str()) ||
+      has_longpath_prefix(path2.c_str())) {
+    return path2;
+  }
+  if (path2.empty()) {
+    return path1;
+  }
+
+  if (is_separator(path1.back())) {
+    return is_separator(path2.front()) ? (path1 + path2.substr(1))
+                                       : (path1 + path2);
+  } else {
+    return is_separator(path2.front()) ? (path1 + path2)
+                                       : (path1 + '\\' + path2);
+  }
+}
+
+string normalize(string path) {
+  if (has_longpath_prefix(path.c_str())) {
+    path = path.substr(4);
+  }
+
+  static const string dot(".");
+  static const string dotdot("..");
+
+  std::vector<string> segments;
+  int segment_start = -1;
+  // Find the path segments in `path` (separated by "/").
+  for (int i = 0;; ++i) {
+    if (!is_separator(path[i]) && path[i] != '\0') {
+      // The current character does not end a segment, so start one unless it's
+      // already started.
+      if (segment_start < 0) {
+        segment_start = i;
+      }
+    } else if (segment_start >= 0 && i > segment_start) {
+      // The current character is "/" or "\0", so this ends a segment.
+      // Add that to `segments` if there's anything to add; handle "." and "..".
+      string segment(path, segment_start, i - segment_start);
+      segment_start = -1;
+      if (segment == dotdot) {
+        if (!segments.empty() &&
+            (!has_drive_letter(segments[0].c_str()) || segments.size() > 1)) {
+          segments.pop_back();
+        }
+      } else if (segment != dot && !segment.empty()) {
+        segments.push_back(segment);
+      }
+    }
+    if (path[i] == '\0') {
+      break;
+    }
+  }
+
+  // Handle the case when `path` is just a drive specifier (or some degenerate
+  // form of it, e.g. "c:\..").
+  if (segments.size() == 1 && segments[0].size() == 2 &&
+      has_drive_letter(segments[0].c_str())) {
+    return segments[0] + '\\';
+  }
+
+  // Join all segments.
+  bool first = true;
+  std::ostringstream result;
+  for (const auto& s : segments) {
+    if (!first) {
+      result << '\\';
+    }
+    first = false;
+    result << s;
+  }
+  // Preserve trailing separator if the input contained it.
+  if (is_separator(path.back())) {
+    result << '\\';
+  }
+  return result.str();
+}
+
+std::unique_ptr<WCHAR[]> as_wstring(const string& s) {
+  int len = ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s.size(), NULL, 0);
+  std::unique_ptr<WCHAR[]> result(new WCHAR[len + 1]);
+  ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s.size(), result.get(), len + 1);
+  result.get()[len] = 0;
+  return std::move(result);
+}
+
+wstring as_wchar_path(const string& path) {
+  std::unique_ptr<WCHAR[]> wbuf(as_wstring(path));
+  replace_directory_separators(wbuf.get());
+  return wstring(wbuf.get());
+}
+
+bool as_windows_path(const string& path, wstring* result) {
+  if (path.empty()) {
+    result->clear();
+    return true;
+  }
+  if (is_separator(path[0]) || is_drive_relative(path.c_str())) {
+    return false;
+  }
+
+  string mutable_path = path;
+  if (!is_path_absolute(mutable_path.c_str()) &&
+      !has_longpath_prefix(mutable_path.c_str())) {
+    char cwd[MAX_PATH];
+    ::GetCurrentDirectoryA(MAX_PATH, cwd);
+    mutable_path = join_paths(cwd, mutable_path);
+  }
+  *result = as_wchar_path(normalize(mutable_path));
+  if (!has_longpath_prefix(result->c_str())) {
+    // Add the "\\?\" prefix unconditionally. This way we prevent the Win32 API
+    // from processing the path and "helpfully" removing trailing dots from the
+    // path, for example.
+    // See https://github.com/bazelbuild/bazel/issues/2935
+    *result = wstring(L"\\\\?\\") + *result;
+  }
+  return true;
+}
+
+}  // namespace
+
+int open(const char* path, int flags, int mode) {
+#ifdef SUPPORT_LONGPATHS
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return -1;
+  }
+  return ::_wopen(wpath.c_str(), flags, mode);
+#else
+  return ::_open(path, flags, mode);
+#endif
+}
+
+int mkdir(const char* path, int _mode) {
+#ifdef SUPPORT_LONGPATHS
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return -1;
+  }
+  return ::_wmkdir(wpath.c_str());
+#else   // not SUPPORT_LONGPATHS
+  return ::_mkdir(path);
+#endif  // not SUPPORT_LONGPATHS
+}
+
+int access(const char* path, int mode) {
+#ifdef SUPPORT_LONGPATHS
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return -1;
+  }
+  return ::_waccess(wpath.c_str(), mode);
+#else
+  return ::_access(path, mode);
+#endif
+}
+
+int chdir(const char* path) {
+#ifdef SUPPORT_LONGPATHS
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return -1;
+  }
+  return ::_wchdir(wpath.c_str());
+#else
+  return ::_chdir(path);
+#endif
+}
+
+int stat(const char* path, struct _stat* buffer) {
+#ifdef SUPPORT_LONGPATHS
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return -1;
+  }
+  return ::_wstat(wpath.c_str(), buffer);
+#else   // not SUPPORT_LONGPATHS
+  return ::_stat(path, buffer);
+#endif  // not SUPPORT_LONGPATHS
+}
+
+FILE* fopen(const char* path, const char* mode) {
+#ifdef SUPPORT_LONGPATHS
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return NULL;
+  }
+  std::unique_ptr<WCHAR[]> wmode(as_wstring(mode));
+  return ::_wfopen(wpath.c_str(), wmode.get());
+#else
+  return ::fopen(path, mode);
+#endif
+}
+
+int close(int fd) { return ::close(fd); }
+
+int dup(int fd) { return ::_dup(fd); }
+
+int dup2(int fd1, int fd2) { return ::_dup2(fd1, fd2); }
+
+int read(int fd, void* buffer, size_t size) {
+  return ::_read(fd, buffer, size);
+}
+
+int setmode(int fd, int mode) { return ::_setmode(fd, mode); }
+
+int write(int fd, const void* buffer, size_t size) {
+  return ::_write(fd, buffer, size);
+}
+
+wstring testonly_path_to_winpath(const string& path) {
+  wstring wpath;
+  as_windows_path(path, &wpath);
+  return wpath;
+}
+
+}  // namespace win32
+}  // namespace internal
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // defined(_WIN32)
+

+ 96 - 0
src/google/protobuf/stubs/io_win32.h

@@ -0,0 +1,96 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: laszlocsomor@google.com (Laszlo Csomor)
+//
+// This file contains the declarations for Windows implementations of
+// commonly used POSIX functions such as open(2) and access(2), as well
+// as macro definitions for flags of these functions.
+//
+// By including this file you'll redefine open/access/etc. to
+// ::google::protobuf::internal::win32::{open/access/etc.}.
+// Make sure you don't include a header that attempts to redeclare or
+// redefine these functions, that'll lead to confusing compilation
+// errors. It's best to #include this file as the last one to ensure that.
+//
+// This file is only used on Windows, it's empty on other platforms.
+
+#ifndef GOOGLE_PROTOBUF_STUBS_IO_WIN32_H__
+#define GOOGLE_PROTOBUF_STUBS_IO_WIN32_H__
+
+#if defined(_WIN32)
+
+#include <string>
+
+namespace google {
+namespace protobuf {
+namespace internal {
+namespace win32 {
+
+FILE* fopen(const char* path, const char* mode);
+int access(const char* path, int mode);
+int chdir(const char* path);
+int close(int fd);
+int dup(int fd);
+int dup2(int fd1, int fd2);
+int mkdir(const char* path, int _mode);
+int open(const char* path, int flags, int mode = 0);
+int read(int fd, void* buffer, size_t size);
+int setmode(int fd, int mode);
+int stat(const char* path, struct _stat* buffer);
+int write(int fd, const void* buffer, size_t size);
+std::wstring testonly_path_to_winpath(const std::string& path);
+
+}  // namespace win32
+}  // namespace internal
+}  // namespace protobuf
+}  // namespace google
+
+#ifndef W_OK
+#define W_OK 02  // not defined by MSVC for whatever reason
+#endif
+
+#ifndef F_OK
+#define F_OK 00  // not defined by MSVC for whatever reason
+#endif
+
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+
+#endif  // defined(_WIN32)
+
+#endif  // GOOGLE_PROTOBUF_STUBS_IO_WIN32_H__
+
+

+ 367 - 0
src/google/protobuf/stubs/io_win32_unittest.cc

@@ -0,0 +1,367 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: laszlocsomor@google.com (Laszlo Csomor)
+//
+// Unit tests for long-path-aware open/mkdir/access on Windows.
+//
+// This file is only used on Windows, it's empty on other platforms.
+
+#if defined(_WIN32)
+
+#define WIN32_LEAN_AND_MEAN
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <wchar.h>
+#include <windows.h>
+
+#include <google/protobuf/stubs/io_win32.h>
+#include <google/protobuf/testing/googletest.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+namespace google {
+namespace protobuf {
+namespace internal {
+namespace win32 {
+namespace {
+
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+class IoWin32Test : public ::testing::Test {
+ public:
+  void SetUp() override;
+  void TearDown() override;
+
+ protected:
+  bool CreateAllUnder(wstring path);
+  bool DeleteAllUnder(wstring path);
+
+  string test_tmpdir;
+  wstring wtest_tmpdir;
+};
+
+#define ASSERT_INITIALIZED              \
+  {                                     \
+    EXPECT_FALSE(test_tmpdir.empty());  \
+    EXPECT_FALSE(wtest_tmpdir.empty()); \
+  }
+
+void IoWin32Test::SetUp() {
+  test_tmpdir = string(TestTempDir());
+  wtest_tmpdir.clear();
+  if (test_tmpdir.empty()) {
+    const char* test_tmpdir_env = getenv("TEST_TMPDIR");
+    if (test_tmpdir_env != nullptr && *test_tmpdir_env) {
+      test_tmpdir = string(test_tmpdir_env);
+    }
+
+    // Only Bazel defines TEST_TMPDIR, CMake does not, so look for other
+    // suitable environment variables.
+    if (test_tmpdir.empty()) {
+      for (const char* name : {"TEMP", "TMP"}) {
+        test_tmpdir_env = getenv(name);
+        if (test_tmpdir_env != nullptr && *test_tmpdir_env) {
+          test_tmpdir = string(test_tmpdir_env);
+          break;
+        }
+      }
+    }
+
+    // No other temp directory was found. Use the current director
+    if (test_tmpdir.empty()) {
+      char buffer[MAX_PATH];
+      // Use GetCurrentDirectoryA instead of GetCurrentDirectoryW, because the
+      // current working directory must always be shorter than MAX_PATH, even
+      // with
+      // "\\?\" prefix (except on Windows 10 version 1607 and beyond, after
+      // opting in to long paths by default [1]).
+      //
+      // [1] https://msdn.microsoft.com/en-us/library/windows/ \
+      //   desktop/aa365247(v=vs.85).aspx#maxpath
+      DWORD result = ::GetCurrentDirectoryA(MAX_PATH, buffer);
+      if (result > 0) {
+        test_tmpdir = string(buffer);
+      } else {
+        // Using assertions in SetUp/TearDown seems to confuse the test
+        // framework, so just leave the member variables empty in case of
+        // failure.
+        GOOGLE_CHECK_OK(false);
+        return;
+      }
+    }
+  }
+
+  while (test_tmpdir.back() == '/' || test_tmpdir.back() == '\\') {
+    test_tmpdir.pop_back();
+  }
+  test_tmpdir += "\\io_win32_unittest.tmp";
+
+  // CreateDirectoryA's limit is 248 chars, see MSDN.
+  // https://msdn.microsoft.com/en-us/library/windows/ \
+  //   desktop/aa363855(v=vs.85).aspx
+  wtest_tmpdir = testonly_path_to_winpath(test_tmpdir);
+  if (!DeleteAllUnder(wtest_tmpdir) || !CreateAllUnder(wtest_tmpdir)) {
+    GOOGLE_CHECK_OK(false);
+    test_tmpdir.clear();
+    wtest_tmpdir.clear();
+  }
+}
+
+void IoWin32Test::TearDown() {
+  if (!wtest_tmpdir.empty()) {
+    DeleteAllUnder(wtest_tmpdir);
+  }
+}
+
+bool IoWin32Test::CreateAllUnder(wstring path) {
+  // Prepend UNC prefix if the path doesn't have it already. Don't bother
+  // checking if the path is shorter than MAX_PATH, let's just do it
+  // unconditionally.
+  if (path.find(L"\\\\?\\") != 0) {
+    path = wstring(L"\\\\?\\") + path;
+  }
+  if (::CreateDirectoryW(path.c_str(), NULL) ||
+      GetLastError() == ERROR_ALREADY_EXISTS ||
+      GetLastError() == ERROR_ACCESS_DENIED) {
+    return true;
+  }
+  if (GetLastError() == ERROR_PATH_NOT_FOUND) {
+    size_t pos = path.find_last_of(L'\\');
+    if (pos != wstring::npos) {
+      wstring parent(path, 0, pos);
+      if (CreateAllUnder(parent) && CreateDirectoryW(path.c_str(), NULL)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool IoWin32Test::DeleteAllUnder(wstring path) {
+  static const wstring kDot(L".");
+  static const wstring kDotDot(L"..");
+
+  // Prepend UNC prefix if the path doesn't have it already. Don't bother
+  // checking if the path is shorter than MAX_PATH, let's just do it
+  // unconditionally.
+  if (path.find(L"\\\\?\\") != 0) {
+    path = wstring(L"\\\\?\\") + path;
+  }
+  // Append "\" if necessary.
+  if (path.back() != '\\') {
+    path.push_back('\\');
+  }
+
+  WIN32_FIND_DATAW metadata;
+  HANDLE handle = ::FindFirstFileW((path + L"*").c_str(), &metadata);
+  if (handle == INVALID_HANDLE_VALUE) {
+    return true;  // directory doesn't exist
+  }
+
+  bool result = true;
+  do {
+    wstring childname = metadata.cFileName;
+    if (kDot != childname && kDotDot != childname) {
+      wstring childpath = path + childname;
+      if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+        // If this is not a junction, delete its contents recursively.
+        // Finally delete this directory/junction too.
+        if (((metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 &&
+             !DeleteAllUnder(childpath)) ||
+            !::RemoveDirectoryW(childpath.c_str())) {
+          result = false;
+          break;
+        }
+      } else {
+        if (!::DeleteFileW(childpath.c_str())) {
+          result = false;
+          break;
+        }
+      }
+    }
+  } while (::FindNextFileW(handle, &metadata));
+  ::FindClose(handle);
+  return result;
+}
+
+TEST_F(IoWin32Test, AccessTest) {
+  ASSERT_INITIALIZED;
+
+  string path = test_tmpdir;
+  while (path.size() < MAX_PATH - 30) {
+    path += "\\accesstest";
+    EXPECT_EQ(mkdir(path.c_str(), 0644), 0);
+  }
+  string file = path + "\\file.txt";
+  int fd = open(file.c_str(), O_CREAT | O_WRONLY, 0644);
+  if (fd > 0) {
+    EXPECT_EQ(close(fd), 0);
+  } else {
+    EXPECT_TRUE(false);
+  }
+
+  EXPECT_EQ(access(test_tmpdir.c_str(), F_OK), 0);
+  EXPECT_EQ(access(path.c_str(), F_OK), 0);
+  EXPECT_EQ(access(path.c_str(), W_OK), 0);
+  EXPECT_EQ(access(file.c_str(), F_OK | W_OK), 0);
+  EXPECT_NE(access((file + ".blah").c_str(), F_OK), 0);
+  EXPECT_NE(access((file + ".blah").c_str(), W_OK), 0);
+
+  EXPECT_EQ(access(".", F_OK), 0);
+  EXPECT_EQ(access(".", W_OK), 0);
+  EXPECT_EQ(access((test_tmpdir + "/accesstest").c_str(), F_OK | W_OK), 0);
+  ASSERT_EQ(access((test_tmpdir + "/./normalize_me/.././accesstest").c_str(),
+                   F_OK | W_OK),
+            0);
+  EXPECT_NE(access("io_win32_unittest.AccessTest.nonexistent", F_OK), 0);
+  EXPECT_NE(access("io_win32_unittest.AccessTest.nonexistent", W_OK), 0);
+
+  ASSERT_EQ(access("c:bad", F_OK), -1);
+  ASSERT_EQ(errno, ENOENT);
+  ASSERT_EQ(access("/tmp/bad", F_OK), -1);
+  ASSERT_EQ(errno, ENOENT);
+  ASSERT_EQ(access("\\bad", F_OK), -1);
+  ASSERT_EQ(errno, ENOENT);
+}
+
+TEST_F(IoWin32Test, OpenTest) {
+  ASSERT_INITIALIZED;
+
+  string path = test_tmpdir;
+  while (path.size() < MAX_PATH) {
+    path += "\\opentest";
+    EXPECT_EQ(mkdir(path.c_str(), 0644), 0);
+  }
+  string file = path + "\\file.txt";
+  int fd = open(file.c_str(), O_CREAT | O_WRONLY, 0644);
+  if (fd > 0) {
+    EXPECT_EQ(write(fd, "hello", 5), 5);
+    EXPECT_EQ(close(fd), 0);
+  } else {
+    EXPECT_TRUE(false);
+  }
+
+  ASSERT_EQ(open("c:bad.txt", O_CREAT | O_WRONLY, 0644), -1);
+  ASSERT_EQ(errno, ENOENT);
+  ASSERT_EQ(open("/tmp/bad.txt", O_CREAT | O_WRONLY, 0644), -1);
+  ASSERT_EQ(errno, ENOENT);
+  ASSERT_EQ(open("\\bad.txt", O_CREAT | O_WRONLY, 0644), -1);
+  ASSERT_EQ(errno, ENOENT);
+}
+
+TEST_F(IoWin32Test, MkdirTest) {
+  ASSERT_INITIALIZED;
+
+  string path = test_tmpdir;
+  do {
+    path += "\\mkdirtest";
+    ASSERT_EQ(mkdir(path.c_str(), 0644), 0);
+  } while (path.size() <= MAX_PATH);
+
+  ASSERT_EQ(mkdir("c:bad", 0644), -1);
+  ASSERT_EQ(errno, ENOENT);
+  ASSERT_EQ(mkdir("/tmp/bad", 0644), -1);
+  ASSERT_EQ(errno, ENOENT);
+  ASSERT_EQ(mkdir("\\bad", 0644), -1);
+  ASSERT_EQ(errno, ENOENT);
+}
+
+TEST_F(IoWin32Test, ChdirTest) {
+  char owd[MAX_PATH];
+  EXPECT_GT(::GetCurrentDirectoryA(MAX_PATH, owd), 0);
+  string path("C:\\");
+  EXPECT_EQ(access(path.c_str(), F_OK), 0);
+  ASSERT_EQ(chdir(path.c_str()), 0);
+  EXPECT_TRUE(::SetCurrentDirectoryA(owd));
+
+  // Do not try to chdir into the test_tmpdir, it may already contain directory
+  // names with trailing dots.
+  // Instead test here with an obviously dot-trailed path. If the win32_chdir
+  // function would not convert the path to absolute and prefix with "\\?\" then
+  // the Win32 API would ignore the trailing dot, but because of the prefixing
+  // there'll be no path processing done, so we'll actually attempt to chdir
+  // into "C:\some\path\foo."
+  path = test_tmpdir + "/foo.";
+  EXPECT_EQ(mkdir(path.c_str(), 644), 0);
+  EXPECT_EQ(access(path.c_str(), F_OK), 0);
+  ASSERT_NE(chdir(path.c_str()), 0);
+}
+
+TEST_F(IoWin32Test, AsWindowsPathTest) {
+  DWORD size = GetCurrentDirectoryW(0, NULL);
+  unique_ptr<wchar_t[]> cwd_str(new wchar_t[size]);
+  EXPECT_GT(GetCurrentDirectoryW(size, cwd_str.get()), 0);
+  wstring cwd = wstring(L"\\\\?\\") + cwd_str.get();
+
+  ASSERT_EQ(testonly_path_to_winpath("relative_mkdirtest"),
+            cwd + L"\\relative_mkdirtest");
+  ASSERT_EQ(testonly_path_to_winpath("preserve//\\trailing///"),
+            cwd + L"\\preserve\\trailing\\");
+  ASSERT_EQ(testonly_path_to_winpath("./normalize_me\\/../blah"),
+            cwd + L"\\blah");
+  std::ostringstream relpath;
+  for (wchar_t* p = cwd_str.get(); *p; ++p) {
+    if (*p == '/' || *p == '\\') {
+      relpath << "../";
+    }
+  }
+  relpath << ".\\/../\\./beyond-toplevel";
+  ASSERT_EQ(testonly_path_to_winpath(relpath.str()),
+            wstring(L"\\\\?\\") + cwd_str.get()[0] + L":\\beyond-toplevel");
+
+  // Absolute unix paths lack drive letters, driveless absolute windows paths
+  // do too. Neither can be converted to a drive-specifying absolute Windows
+  // path.
+  ASSERT_EQ(testonly_path_to_winpath("/absolute/unix/path"), L"");
+  // Though valid on Windows, we also don't support UNC paths (\\UNC\\blah).
+  ASSERT_EQ(testonly_path_to_winpath("\\driveless\\absolute"), L"");
+  // Though valid in cmd.exe, drive-relative paths are not supported.
+  ASSERT_EQ(testonly_path_to_winpath("c:foo"), L"");
+  ASSERT_EQ(testonly_path_to_winpath("c:/foo"), L"\\\\?\\c:\\foo");
+}
+
+}  // namespace
+}  // namespace win32
+}  // namespace internal
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // defined(_WIN32)
+

+ 13 - 6
src/google/protobuf/testing/file.cc

@@ -38,24 +38,28 @@
 #ifdef _MSC_VER
 #define WIN32_LEAN_AND_MEAN  // yeah, right
 #include <windows.h>         // Find*File().  :(
-#include <io.h>
-#include <direct.h>
+// #include <direct.h>
 #else
 #include <dirent.h>
 #include <unistd.h>
 #endif
 #include <errno.h>
 
+#include <google/protobuf/stubs/io_win32.h>
+
 namespace google {
 namespace protobuf {
 
 #ifdef _WIN32
-#define mkdir(name, mode) mkdir(name)
 // Windows doesn't have symbolic links.
 #define lstat stat
-#ifndef F_OK
-#define F_OK 00  // not defined by MSVC for whatever reason
-#endif
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::access;
+using google::protobuf::internal::win32::chdir;
+using google::protobuf::internal::win32::fopen;
+using google::protobuf::internal::win32::mkdir;
+using google::protobuf::internal::win32::stat;
 #endif
 
 bool File::Exists(const string& name) {
@@ -113,6 +117,9 @@ void File::WriteStringToFileOrDie(const string& contents, const string& name) {
 }
 
 bool File::CreateDir(const string& name, int mode) {
+  if (!name.empty()) {
+    GOOGLE_CHECK_OK(name.back() != '.');
+  }
   return mkdir(name.c_str(), mode) == 0;
 }
 

+ 30 - 6
src/google/protobuf/testing/googletest.cc

@@ -33,14 +33,14 @@
 
 #include <google/protobuf/testing/googletest.h>
 #include <google/protobuf/testing/file.h>
+#include <google/protobuf/stubs/io_win32.h>
 #include <google/protobuf/stubs/strutil.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <errno.h>
 #include <stdlib.h>
 #ifdef _MSC_VER
-#include <io.h>
-#include <direct.h>
+// #include <direct.h>
 #else
 #include <unistd.h>
 #endif
@@ -53,7 +53,13 @@ namespace google {
 namespace protobuf {
 
 #ifdef _WIN32
-#define mkdir(name, mode) mkdir(name)
+// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
+// them like we do below.
+using google::protobuf::internal::win32::close;
+using google::protobuf::internal::win32::dup2;
+using google::protobuf::internal::win32::dup;
+using google::protobuf::internal::win32::mkdir;
+using google::protobuf::internal::win32::open;
 #endif
 
 #ifndef O_BINARY
@@ -111,14 +117,32 @@ string GetTemporaryDirectoryName() {
   char b[L_tmpnam + 1];     // HPUX multithread return 0 if s is 0
   string result = tmpnam(b);
 #ifdef _WIN32
+  // Avoid a trailing dot by changing it to an underscore. On Win32 the names of
+  // files and directories can, but should not, end with dot.
+  //
+  // In MS-DOS and FAT16 filesystem the filenames were 8dot3 style so it didn't
+  // make sense to have a name ending in dot without an extension, so the shell
+  // silently ignored trailing dots. To this day the Win32 API still maintains
+  // this behavior and silently ignores trailing dots in path arguments of
+  // functions such as CreateFile{A,W}. Even POSIX API function implementations
+  // seem to wrap the Win32 API functions (e.g. CreateDirectoryA) and behave
+  // this way.
+  // It's possible to avoid this behavior and create files / directories with
+  // trailing dots (using CreateFileW / CreateDirectoryW and prefixing the path
+  // with "\\?\") but these will be degenerate in the sense that you cannot
+  // chdir into such directories (or navigate into them with Windows Explorer)
+  // nor can you open such files with some programs (e.g. Notepad).
+  if (result.back() == '.') {
+    result[result.size() - 1] = '_';
+  }
   // On Win32, tmpnam() returns a file prefixed with '\', but which is supposed
   // to be used in the current working directory.  WTF?
   if (HasPrefixString(result, "\\")) {
     result.erase(0, 1);
   }
-  // The Win32 API accepts forward slashes as a path delimiter even though
-  // backslashes are standard.  Let's avoid confusion and use only forward
-  // slashes.
+  // The Win32 API accepts forward slashes as a path delimiter as long as the
+  // path doesn't use the "\\?\" prefix.
+  // Let's avoid confusion and use only forward slashes.
   result = StringReplace(result, "\\", "/", true);
 #endif  // _WIN32
   return result;