aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2017-03-30 14:12:44 +0200
committerGravatar Laszlo Csomor <laszlocsomor@google.com>2017-07-11 08:31:54 +0200
commite05e777d468c2486cea53764f1ac70859b5e98c4 (patch)
tree77cf85f401633bf95a42c4556dfe0e54878b9cec /src
parent9ab7c73f7c989afa777a40ff2a7c5f0f138be22c (diff)
Windows: support long paths
Add implementations of open(2), mkdir(2), stat(2), etc. that support long paths under Windows (paths longer than MAX_PATH in <windows.h>, which is 260 characters). The implementations are in a separate namespace (google::protobuf::internal::win32), so they won't collide with the standard implementations in <io.h>, but after importing them with `using` they can be drop-in replacements. Fixes https://github.com/bazelbuild/bazel/issues/2634 Fixes https://github.com/google/protobuf/issues/2891
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/google/protobuf/compiler/command_line_interface.cc38
-rw-r--r--src/google/protobuf/compiler/command_line_interface_unittest.cc28
-rw-r--r--src/google/protobuf/compiler/importer.cc18
-rw-r--r--src/google/protobuf/compiler/objectivec/objectivec_helpers.cc11
-rw-r--r--src/google/protobuf/compiler/plugin.cc17
-rw-r--r--src/google/protobuf/io/zero_copy_stream_impl.cc13
-rw-r--r--src/google/protobuf/io/zero_copy_stream_unittest.cc11
-rw-r--r--src/google/protobuf/message_unittest.cc12
-rw-r--r--src/google/protobuf/stubs/io_win32.cc362
-rw-r--r--src/google/protobuf/stubs/io_win32.h96
-rw-r--r--src/google/protobuf/stubs/io_win32_unittest.cc367
-rw-r--r--src/google/protobuf/testing/file.cc19
-rw-r--r--src/google/protobuf/testing/googletest.cc36
14 files changed, 950 insertions, 81 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 45a8a79b..8d48f293 100644
--- a/src/Makefile.am
+++ b/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 \
@@ -761,6 +763,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 \
diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc
index df03907d..4d894c82 100644
--- a/src/google/protobuf/compiler/command_line_interface.cc
+++ b/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.)
diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc
index 366e623f..5fbb37a4 100644
--- a/src/google/protobuf/compiler/command_line_interface_unittest.cc
+++ b/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) {
diff --git a/src/google/protobuf/compiler/importer.cc b/src/google/protobuf/compiler/importer.cc
index 844edc1c..71e3c6de 100644
--- a/src/google/protobuf/compiler/importer.cc
+++ b/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?
diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
index e347ef1e..72b5d5f5 100644
--- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
+++ b/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.
diff --git a/src/google/protobuf/compiler/plugin.cc b/src/google/protobuf/compiler/plugin.cc
index 3848101d..e8ffbb51 100644
--- a/src/google/protobuf/compiler/plugin.cc
+++ b/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;
diff --git a/src/google/protobuf/io/zero_copy_stream_impl.cc b/src/google/protobuf/io/zero_copy_stream_impl.cc
index 109c55c1..c7372ac2 100644
--- a/src/google/protobuf/io/zero_copy_stream_impl.cc
+++ b/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 {
diff --git a/src/google/protobuf/io/zero_copy_stream_unittest.cc b/src/google/protobuf/io/zero_copy_stream_unittest.cc
index 235cbca4..f3754087 100644
--- a/src/google/protobuf/io/zero_copy_stream_unittest.cc
+++ b/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
diff --git a/src/google/protobuf/message_unittest.cc b/src/google/protobuf/message_unittest.cc
index 0469f4ca..cbf9051a 100644
--- a/src/google/protobuf/message_unittest.cc
+++ b/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
diff --git a/src/google/protobuf/stubs/io_win32.cc b/src/google/protobuf/stubs/io_win32.cc
new file mode 100644
index 00000000..6f0295e1
--- /dev/null
+++ b/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)
+
diff --git a/src/google/protobuf/stubs/io_win32.h b/src/google/protobuf/stubs/io_win32.h
new file mode 100644
index 00000000..daccf16c
--- /dev/null
+++ b/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__
+
+
diff --git a/src/google/protobuf/stubs/io_win32_unittest.cc b/src/google/protobuf/stubs/io_win32_unittest.cc
new file mode 100644
index 00000000..90bd9c96
--- /dev/null
+++ b/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)
+
diff --git a/src/google/protobuf/testing/file.cc b/src/google/protobuf/testing/file.cc
index 470512ed..a1850e44 100644
--- a/src/google/protobuf/testing/file.cc
+++ b/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;
}
diff --git a/src/google/protobuf/testing/googletest.cc b/src/google/protobuf/testing/googletest.cc
index d45706b6..c6fb00d4 100644
--- a/src/google/protobuf/testing/googletest.cc
+++ b/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;