Преглед изворни кода

Windows: expand path names on command line

Fixes https://github.com/google/protobuf/issues/3957
Laszlo Csomor пре 7 година
родитељ
комит
414a625a79

+ 1 - 19
BUILD

@@ -362,25 +362,7 @@ cc_library(
     ],
     copts = COPTS,
     includes = ["src/"],
-    linkopts = LINK_OPTS + select({
-        ":msvc": [
-            # Linking to setargv.obj makes the default command line argument
-            # parser expand wildcards, so the main method's argv will contain the
-            # expanded list instead of the wildcards.
-            #
-            # Adding dummy "-DEFAULTLIB:kernel32.lib", because:
-            # - Microsoft ships this object file next to default libraries
-            # - but this file is not a library, just a precompiled object
-            # - "-WHOLEARCHIVE" and "-DEFAULTLIB" only accept library,
-            #   not precompiled object.
-            # - Bazel would assume linkopt that does not start with "-" or "$"
-            #   as a label to a target, so we add a harmless "-DEFAULTLIB:kernel32.lib"
-            #   before "setargv.obj".
-            # See https://msdn.microsoft.com/en-us/library/8bch7bkk.aspx
-            "-DEFAULTLIB:kernel32.lib setargv.obj",
-        ],
-        "//conditions:default": [],
-    }),
+    linkopts = LINK_OPTS,
     visibility = ["//visibility:public"],
     deps = [":protobuf"],
 )

+ 25 - 0
src/google/protobuf/compiler/command_line_interface.cc

@@ -1388,7 +1388,32 @@ CommandLineInterface::InterpretArgument(const string& name,
       return PARSE_ARGUMENT_FAIL;
     }
 
+#if defined(_WIN32)
+    // On Windows, the shell (typically cmd.exe) does not expand wildcards in
+    // file names (e.g. foo\*.proto), so we do it ourselves.
+    switch (google::protobuf::internal::win32::expand_wildcards(
+          value,
+          [this](const string& path) {
+            this->input_files_.push_back(path);
+          })) {
+      case google::protobuf::internal::win32::ExpandWildcardsResult::kSuccess:
+        break;
+      case google::protobuf::internal::win32::ExpandWildcardsResult::kErrorNoMatchingFile:
+        // Path does not exist, is not a file, or it's longer than MAX_PATH and
+        // long path handling is disabled.
+        std::cerr << "Invalid file name pattern or missing input file \""
+                  << value << "\"" << std::endl;
+        return PARSE_ARGUMENT_FAIL;
+      default:
+        std::cerr << "Cannot convert path \"" << value
+                  << "\" to or from Windows style" << std::endl;
+        return PARSE_ARGUMENT_FAIL;
+    }
+#else  // not _WIN32
+    // On other platforms than Windows (e.g. Linux, Mac OS) the shell (typically
+    // Bash) expands wildcards.
     input_files_.push_back(value);
+#endif  // _WIN32
 
   } else if (name == "-I" || name == "--proto_path") {
     if (!descriptor_set_in_names_.empty()) {

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

@@ -56,6 +56,11 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <wctype.h>
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+
 #include <windows.h>
 
 #include <google/protobuf/stubs/io_win32.h>
@@ -355,6 +360,92 @@ wstring testonly_utf8_to_winpath(const char* path) {
   return as_windows_path(path, &wpath) ? wpath : wstring();
 }
 
+bool expand_wildcards(
+    const string& path, std::function<void(const string&)> consume) {
+  if (path.find_first_of("*?") == string::npos) {
+    // There are no wildcards in the path, we don't need to expand it.
+    consume(path);
+    return ExpandWildcardsResult::kSuccess;
+  }
+
+#ifdef SUPPORT_LONGPATHS
+
+  wstring wpath;
+  if (!as_windows_path(path.c_str(), &wpath)) {
+    return ExpandWildcardsResult::kErrorInputPathConversion;
+  }
+
+  static const wstring kDot = L".";
+  static const wstring kDotDot = L"..";
+  WIN32_FIND_DATAW metadata;
+  HANDLE handle = ::FindFirstFileW(wpath.c_str(), &metadata);
+  if (handle == INVALID_HANDLE_VALUE) {
+    // The pattern does not match any files (or directories).
+    return ExpandWildcardsResult::kErrorNoMatchingFile;
+  }
+
+  string::size_type pos = path.find_last_of("\\/");
+  string dirname;
+  if (pos != string::npos) {
+    dirname = path.substr(0, pos + 1);
+  }
+
+  int matched = ExpandWildcardsResult::kErrorNoMatchingFile;
+  do {
+    // Ignore ".", "..", and directories.
+    if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0
+        && kDot != metadata.cFileName && kDotDot != metadata.cFileName) {
+      matched = ExpandWildcardsResult::kSuccess;
+      string filename;
+      if (!strings::wcs_to_utf8(metadata.cFileName, &filename)) {
+        return ExpandWildcardsResult::kErrorOutputPathConversion;
+      }
+
+      if (dirname.empty()) {
+        consume(filename);
+      } else {
+        consume(dirname + filename);
+      }
+    }
+  } while (::FindNextFileW(handle, &metadata));
+  FindClose(handle);
+  return matched;
+
+#else  // not SUPPORT_LONGPATHS
+
+  static const string kDot = ".";
+  static const string kDotDot = "..";
+  WIN32_FIND_DATAA metadata;
+  HANDLE handle = ::FindFirstFileA(path.c_str(), &metadata);
+  if (handle == INVALID_HANDLE_VALUE) {
+    // The pattern does not match any files (or directories).
+    return ExpandWildcardsResult::kErrorNoMatchingFile;
+  }
+
+  string::size_type pos = path.find_last_of("\\/");
+  string dirname;
+  if (pos != string::npos) {
+    dirname = path.substr(0, pos + 1);
+  }
+
+  int matched = ExpandWildcardsResult::kErrorNoMatchingFile;
+  do {
+    // Ignore ".", "..", and directories.
+    if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0
+        && kDot != metadata.cFileName && kDotDot != metadata.cFileName) {
+      matched = ExpandWildcardsResult::kSuccess;
+      if (!dirname.empty()) {
+        consume(dirname + metadata.cFileName);
+      } else {
+        consume(metadata.cFileName);
+      }
+    }
+  } while (::FindNextFileA(handle, &metadata));
+  FindClose(handle);
+  return matched;
+#endif  // SUPPORT_LONGPATHS
+}
+
 namespace strings {
 
 bool wcs_to_mbs(const WCHAR* s, string* out, bool outUtf8) {

+ 505 - 0
src/google/protobuf/stubs/io_win32.cc~

@@ -0,0 +1,505 @@
+// 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/access/etc. on Windows, as well
+// as for the supporting utility functions.
+//
+// These functions convert the input path to an absolute Windows path
+// with "\\?\" prefix, then pass that to _wopen/_wmkdir/_waccess/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 <memory>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <wctype.h>
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+
+#include <windows.h>
+
+#include <google/protobuf/stubs/io_win32.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace google {
+namespace protobuf {
+namespace internal {
+namespace win32 {
+namespace {
+
+using std::string;
+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); }
+};
+
+template <typename char_type>
+bool null_or_empty(const char_type* s) {
+  return s == nullptr || *s == 0;
+}
+
+// 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] == '\\';
+}
+
+template <typename char_type>
+bool is_separator(char_type c) {
+  return c == '/' || c == '\\';
+}
+
+// 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_drive_relative(const char_type* path) {
+  return has_drive_letter(path) && (path[2] == 0 || !is_separator(path[2]));
+}
+
+wstring join_paths(const wstring& path1, const wstring& 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[path1.size() - 1])) {
+    return is_separator(path2[0]) ? (path1 + path2.substr(1))
+                                       : (path1 + path2);
+  } else {
+    return is_separator(path2[0]) ? (path1 + path2)
+                                       : (path1 + L'\\' + path2);
+  }
+}
+
+wstring normalize(wstring path) {
+  if (has_longpath_prefix(path.c_str())) {
+    path = path.substr(4);
+  }
+
+  static const wstring dot(L".");
+  static const wstring dotdot(L"..");
+  const WCHAR* p = path.c_str();
+
+  std::vector<wstring> segments;
+  int segment_start = -1;
+  // Find the path segments in `path` (separated by "/").
+  for (int i = 0;; ++i) {
+    if (!is_separator(p[i]) && p[i] != L'\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 "..".
+      wstring segment(p, 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 (p[i] == L'\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] + L'\\';
+  }
+
+  // Join all segments.
+  bool first = true;
+  std::wstringstream result;
+  for (int i = 0; i < segments.size(); ++i) {
+    if (!first) {
+      result << L'\\';
+    }
+    first = false;
+    result << segments[i];
+  }
+  // Preserve trailing separator if the input contained it.
+  if (!path.empty() && is_separator(p[path.size() - 1])) {
+    result << L'\\';
+  }
+  return result.str();
+}
+
+bool as_windows_path(const char* path, wstring* result) {
+  if (null_or_empty(path)) {
+    result->clear();
+    return true;
+  }
+  wstring wpath;
+  if (!strings::utf8_to_wcs(path, &wpath)) {
+    return false;
+  }
+  if (has_longpath_prefix(wpath.c_str())) {
+    *result = wpath;
+    return true;
+  }
+  if (is_separator(path[0]) || is_drive_relative(path)) {
+    return false;
+  }
+
+
+  if (!is_path_absolute(wpath.c_str())) {
+    int size = ::GetCurrentDirectoryW(0, nullptr);
+    if (size == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+      return false;
+    }
+    std::unique_ptr<WCHAR[]> wcwd(new WCHAR[size]);
+    ::GetCurrentDirectoryW(size, wcwd.get());
+    wpath = join_paths(wcwd.get(), wpath);
+  }
+  wpath = normalize(wpath);
+  if (!has_longpath_prefix(wpath.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
+    wpath = wstring(L"\\\\?\\") + wpath;
+  }
+  *result = wpath;
+  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
+  if (null_or_empty(path)) {
+    errno = EINVAL;
+    return nullptr;
+  }
+  wstring wpath;
+  if (!as_windows_path(path, &wpath)) {
+    errno = ENOENT;
+    return nullptr;
+  }
+  wstring wmode;
+  if (!strings::utf8_to_wcs(mode, &wmode)) {
+    errno = EINVAL;
+    return nullptr;
+  }
+  return ::_wfopen(wpath.c_str(), wmode.c_str());
+#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_utf8_to_winpath(const char* path) {
+  wstring wpath;
+  return as_windows_path(path, &wpath) ? wpath : wstring();
+}
+
+bool expand_wildcards(
+    const string& path, std::function<void(const string&)> consume) {
+  if (path.find_first_of("*?") == string::npos) {
+    // There are no wildcards in the path, we don't need to expand it.
+    consume(path);
+    return ExpandWildcardsResult::kSuccess;
+  }
+
+#ifdef SUPPORT_LONGPATHS
+
+  wstring wpath;
+  if (!as_windows_path(path.c_str(), &wpath)) {
+    return ExpandWildcardsResult::kErrorInputPathConversion;
+  }
+
+  static const wstring kDot = L".";
+  static const wstring kDotDot = L"..";
+  WIN32_FIND_DATAW metadata;
+  HANDLE handle = ::FindFirstFileW(wpath.c_str(), &metadata);
+  if (handle == INVALID_HANDLE_VALUE) {
+    // The pattern does not match any files (or directories).
+    return ExpandWildcardsResult::kErrorNoMatchingFile;
+  }
+
+  string::size_type pos = path.find_last_of("\\/");
+  string dirname;
+  if (pos != string::npos) {
+    dirname = path.substr(0, pos + 1);
+  }
+
+  int matched = ExpandWildcardsResult::kErrorNoMatchingFile;
+  do {
+    // Ignore ".", "..", and directories.
+    if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0
+        && kDot != metadata.cFileName && kDotDot != metadata.cFileName) {
+      matched = ExpandWildcardsResult::kSuccess;
+      string filename;
+      if (!strings::wcs_to_utf8(metadata.cFileName, &filename)) {
+        return ExpandWildcardsResult::kErrorOutputPathConversion;
+      }
+
+      if (dirname.empty()) {
+        consume(filename);
+      } else {
+        consume(dirname + filename);
+      }
+    }
+  } while (::FindNextFileW(handle, &metadata));
+  FindClose(handle);
+  return matched;
+
+#else  // not SUPPORT_LONGPATHS
+
+  static const string kDot = ".";
+  static const string kDotDot = "..";
+  WIN32_FIND_DATAA metadata;
+  HANDLE handle = ::FindFirstFileA(path.c_str(), &metadata);
+  if (handle == INVALID_HANDLE_VALUE) {
+    // The pattern does not match any files (or directories).
+    return ExpandWildcardsResult::kErrorNoMatchingFile;
+  }
+
+  string::size_type pos = path.find_last_of("\\/");
+  string dirname;
+  if (pos != string::npos) {
+    dirname = path.substr(0, pos + 1);
+  }
+
+  int matched = ExpandWildcardsResult::kErrorNoMatchingFile;
+  do {
+    // Ignore ".", "..", and directories.
+    if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0
+        && kDot != metadata.cFileName && kDotDot != metadata.cFileName) {
+      matched = ExpandWildcardsResult::kSuccess;
+      if (!dirname.empty()) {
+        consume(dirname + metadata.cFileName);
+      } else {
+        consume(metadata.cFileName);
+      }
+    }
+  } while (::FindNextFileA(handle, &metadata));
+  FindClose(handle);
+  return matched;
+#endif  // SUPPORT_LONGPATHS
+}
+
+namespace strings {
+
+bool wcs_to_mbs(const WCHAR* s, string* out, bool outUtf8) {
+  if (null_or_empty(s)) {
+    out->clear();
+    return true;
+  }
+  BOOL usedDefaultChar = FALSE;
+  SetLastError(0);
+  int size = WideCharToMultiByte(
+      outUtf8 ? CP_UTF8 : CP_ACP, 0, s, -1, nullptr, 0, nullptr,
+      outUtf8 ? nullptr : &usedDefaultChar);
+  if ((size == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+      || usedDefaultChar) {
+    return false;
+  }
+  std::unique_ptr<CHAR[]> astr(new CHAR[size]);
+  WideCharToMultiByte(
+      outUtf8 ? CP_UTF8 : CP_ACP, 0, s, -1, astr.get(), size, nullptr, nullptr);
+  out->assign(astr.get());
+  return true;
+}
+
+bool mbs_to_wcs(const char* s, wstring* out, bool inUtf8) {
+  if (null_or_empty(s)) {
+    out->clear();
+    return true;
+  }
+
+  SetLastError(0);
+  int size =
+      MultiByteToWideChar(inUtf8 ? CP_UTF8 : CP_ACP, 0, s, -1, nullptr, 0);
+  if (size == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+    return false;
+  }
+  std::unique_ptr<WCHAR[]> wstr(new WCHAR[size]);
+  MultiByteToWideChar(
+      inUtf8 ? CP_UTF8 : CP_ACP, 0, s, -1, wstr.get(), size + 1);
+  out->assign(wstr.get());
+  return true;
+}
+
+bool utf8_to_wcs(const char* input, wstring* out) {
+  return mbs_to_wcs(input, out, true);
+}
+
+bool wcs_to_utf8(const wchar_t* input, string* out) {
+  return wcs_to_mbs(input, out, true);
+}
+
+}  // namespace strings
+}  // namespace win32
+}  // namespace internal
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // defined(_WIN32)

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

@@ -47,6 +47,7 @@
 
 #if defined(_WIN32)
 
+#include <functional>
 #include <string>
 #include <google/protobuf/stubs/port.h>
 
@@ -71,6 +72,30 @@ LIBPROTOBUF_EXPORT int stat(const char* path, struct _stat* buffer);
 LIBPROTOBUF_EXPORT int write(int fd, const void* buffer, size_t size);
 LIBPROTOBUF_EXPORT std::wstring testonly_utf8_to_winpath(const char* path);
 
+struct ExpandWildcardsResult {
+  enum {
+    kSuccess = 0,
+    kErrorNoMatchingFile = 1,
+    kErrorInputPathConversion = 2,
+    kErrorOutputPathConversion = 3,
+  };
+};
+
+// Expand wildcards in a path pattern, feed the result to a consumer function.
+//
+// `path` must be a valid, Windows-style path. It may be absolute, or relative
+// to the current working directory, and it may contain wildcards ("*" and "?")
+// in the last path segment. This function passes all matching file names to
+// `consume`. The resulting paths may not be absolute nor normalized.
+//
+// The function returns true if the path did not contain any wildcards (in
+// which case the path may or may not exist), or the path did contain wildcards
+// and it matched at least one file.
+// The function returns false if the path contained wildcards but it did not
+// match any files.
+LIBPROTOBUF_EXPORT bool expand_wildcards(
+    const std::string& path, std::function<void(const std::string&)> consume);
+
 namespace strings {
 
 // Convert from UTF-16 to Active-Code-Page-encoded or to UTF-8-encoded text.

+ 140 - 0
src/google/protobuf/stubs/io_win32.h~

@@ -0,0 +1,140 @@
+// 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 <functional>
+#include <string>
+#include <google/protobuf/stubs/port.h>
+
+// Compilers on Windows other than MSVC (e.g. Cygwin, MinGW32) define the
+// following functions already, except for mkdir.
+namespace google {
+namespace protobuf {
+namespace internal {
+namespace win32 {
+
+LIBPROTOBUF_EXPORT FILE* fopen(const char* path, const char* mode);
+LIBPROTOBUF_EXPORT int access(const char* path, int mode);
+LIBPROTOBUF_EXPORT int chdir(const char* path);
+LIBPROTOBUF_EXPORT int close(int fd);
+LIBPROTOBUF_EXPORT int dup(int fd);
+LIBPROTOBUF_EXPORT int dup2(int fd1, int fd2);
+LIBPROTOBUF_EXPORT int mkdir(const char* path, int _mode);
+LIBPROTOBUF_EXPORT int open(const char* path, int flags, int mode = 0);
+LIBPROTOBUF_EXPORT int read(int fd, void* buffer, size_t size);
+LIBPROTOBUF_EXPORT int setmode(int fd, int mode);
+LIBPROTOBUF_EXPORT int stat(const char* path, struct _stat* buffer);
+LIBPROTOBUF_EXPORT int write(int fd, const void* buffer, size_t size);
+LIBPROTOBUF_EXPORT std::wstring testonly_utf8_to_winpath(const char* path);
+
+struct ExpandWildcardsResult {
+  enum {
+    kSuccess = 0,
+    kErrorNoMatchingFile = 1,
+    kErrorInputPathConversion = 2,
+    kErrorResultPathConversion = 3,
+  };
+};
+
+// Expand wildcards in a path pattern, feed the result to a consumer function.
+//
+// `path` must be a valid, Windows-style path. It may be absolute, or relative
+// to the current working directory, and it may contain wildcards ("*" and "?")
+// in the last path segment. This function passes all matching file names to
+// `consume`. The resulting paths may not be absolute nor normalized.
+//
+// The function returns true if the path did not contain any wildcards (in
+// which case the path may or may not exist), or the path did contain wildcards
+// and it matched at least one file.
+// The function returns false if the path contained wildcards but it did not
+// match any files.
+LIBPROTOBUF_EXPORT bool expand_wildcards(
+    const std::string& path, std::function<void(const std::string&)> consume);
+
+namespace strings {
+
+// Convert from UTF-16 to Active-Code-Page-encoded or to UTF-8-encoded text.
+LIBPROTOBUF_EXPORT bool wcs_to_mbs(
+    const wchar_t* s, std::string* out, bool outUtf8);
+
+// Convert from Active-Code-Page-encoded or UTF-8-encoded text to UTF-16.
+LIBPROTOBUF_EXPORT bool mbs_to_wcs(
+    const char* s, std::wstring* out, bool inUtf8);
+
+// Convert from UTF-8-encoded text to UTF-16.
+LIBPROTOBUF_EXPORT bool utf8_to_wcs(const char* input, std::wstring* out);
+
+// Convert from UTF-16-encoded text to UTF-8.
+LIBPROTOBUF_EXPORT bool wcs_to_utf8(const wchar_t* input, std::string* out);
+
+}  // namespace strings
+
+}  // 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__

+ 189 - 2
src/google/protobuf/stubs/io_win32_unittest.cc

@@ -53,6 +53,7 @@
 #include <memory>
 #include <sstream>
 #include <string>
+#include <vector>
 
 namespace google {
 namespace protobuf {
@@ -82,6 +83,7 @@ const wchar_t kUtf16Text[] = {
 };
 
 using std::string;
+using std::vector;
 using std::wstring;
 
 class IoWin32Test : public ::testing::Test {
@@ -143,12 +145,24 @@ bool GetCwdAsUtf8(string* result) {
   }
 }
 
+bool CreateEmptyFile(const wstring& path) {
+  HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
+                         FILE_ATTRIBUTE_NORMAL, NULL);
+  if (h == INVALID_HANDLE_VALUE) {
+    return false;
+  }
+  CloseHandle(h);
+  return true;
+}
+
 }  // namespace
 
 void IoWin32Test::SetUp() {
   test_tmpdir.clear();
   wtest_tmpdir.clear();
-  EXPECT_GT(::GetCurrentDirectoryW(MAX_PATH, working_directory), 0);
+  DWORD size = ::GetCurrentDirectoryW(MAX_PATH, working_directory);
+  EXPECT_GT(size, 0);
+  EXPECT_LT(size, MAX_PATH);
 
   string tmp;
   bool ok = false;
@@ -351,7 +365,7 @@ TEST_F(IoWin32Test, MkdirTestNonAscii) {
   ASSERT_INITIALIZED;
 
   // Create a non-ASCII path.
-  // Ensure that we can create the directory using SetCurrentDirectoryW.
+  // Ensure that we can create the directory using CreateDirectoryW.
   EXPECT_TRUE(CreateDirectoryW((wtest_tmpdir + L"\\1").c_str(), nullptr));
   EXPECT_TRUE(CreateDirectoryW((wtest_tmpdir + L"\\1\\" + kUtf16Text).c_str(), nullptr));
   // Ensure that we can create a very similarly named directory using mkdir.
@@ -399,6 +413,179 @@ TEST_F(IoWin32Test, ChdirTestNonAscii) {
   ASSERT_EQ(wNonAscii, cwd);
 }
 
+TEST_F(IoWin32Test, ExpandWildcardsInRelativePathTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_b.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\bar.proto"));
+  // `cd` into `wtest_tmpdir`.
+  EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str()));
+
+  int found_a = 0;
+  int found_b = 0;
+  vector<string> found_bad;
+  // Assert matching a relative path pattern. Results should also be relative.
+  int result = 
+      expand_wildcards(
+          string(kUtf8Text) + "\\foo*.proto",
+          [&found_a, &found_b, &found_bad](const string& p) {
+            if (p == string(kUtf8Text) + "\\foo_a.proto") {
+              found_a++;
+            } else if (p == string(kUtf8Text) + "\\foo_b.proto") {
+              found_b++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  EXPECT_EQ(found_b, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+
+  // Assert matching the exact filename.
+  found_a = 0;
+  found_bad.clear();
+  result = 
+      expand_wildcards(
+          string(kUtf8Text) + "\\foo_a.proto",
+          [&found_a, &found_bad](const string& p) {
+            if (p == string(kUtf8Text) + "\\foo_a.proto") {
+              found_a++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsInAbsolutePathTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_b.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\bar.proto"));
+
+  int found_a = 0;
+  int found_b = 0;
+  vector<string> found_bad;
+  // Assert matching an absolute path. The results should also use absolute
+  // path.
+  int result = 
+      expand_wildcards(
+          string(test_tmpdir) + "\\" + kUtf8Text + "\\foo*.proto",
+          [this, &found_a, &found_b, &found_bad](const string& p) {
+            if (p == string(this->test_tmpdir)
+                         + "\\"
+                         + kUtf8Text
+                         + "\\foo_a.proto") {
+              found_a++;
+            } else if (p == string(this->test_tmpdir)
+                                + "\\"
+                                + kUtf8Text
+                                + "\\foo_b.proto") {
+              found_b++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  EXPECT_EQ(found_b, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+
+  // Assert matching the exact filename.
+  found_a = 0;
+  found_bad.clear();
+  result = 
+      expand_wildcards(
+          string(test_tmpdir) + "\\" + kUtf8Text + "\\foo_a.proto",
+          [this, &found_a, &found_bad](const string& p) {
+            if (p == string(this->test_tmpdir)
+                         + "\\"
+                         + kUtf8Text
+                         + "\\foo_a.proto") {
+              found_a++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsIgnoresDirectoriesTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  EXPECT_TRUE(CreateDirectoryW((wNonAscii + L"\\foo_b.proto").c_str(), nullptr));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_c.proto"));
+  // `cd` into `wtest_tmpdir`.
+  EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str()));
+
+  int found_a = 0;
+  int found_c = 0;
+  vector<string> found_bad;
+  // Assert that the pattern matches exactly the expected files, and using the
+  // absolute path as did the input pattern.
+  int result = 
+      expand_wildcards(
+          string(kUtf8Text) + "\\foo*.proto",
+          [&found_a, &found_c, &found_bad](const string& p) {
+            if (p == string(kUtf8Text) + "\\foo_a.proto") {
+              found_a++;
+            } else if (p == string(kUtf8Text) + "\\foo_c.proto") {
+              found_c++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  EXPECT_EQ(found_c, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsFailsIfNoFileMatchesTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  // `cd` into `wtest_tmpdir`.
+  EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str()));
+
+  // Control test: should match foo*.proto
+  int result = expand_wildcards(
+      string(kUtf8Text) + "\\foo*.proto", [](const string&) {});
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+
+  // Control test: should match foo_a.proto
+  result = expand_wildcards(
+      string(kUtf8Text) + "\\foo_a.proto", [](const string&) {});
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+
+  // Actual test: should not match anything.
+  result = expand_wildcards(
+      string(kUtf8Text) + "\\bar*.proto", [](const string&) {});
+  ASSERT_EQ(result, ExpandWildcardsResult::kErrorNoMatchingFile);
+}
+
 TEST_F(IoWin32Test, AsWindowsPathTest) {
   DWORD size = GetCurrentDirectoryW(0, nullptr);
   std::unique_ptr<wchar_t[]> cwd_str(new wchar_t[size]);

+ 639 - 0
src/google/protobuf/stubs/io_win32_unittest.cc~

@@ -0,0 +1,639 @@
+// 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/etc. on Windows, as well as
+// for the supporting utility functions.
+//
+// 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 <gtest/gtest.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace google {
+namespace protobuf {
+namespace internal {
+namespace win32 {
+namespace {
+
+const char kUtf8Text[] = {
+    'h', 'i', ' ',
+    // utf-8: 11010000 10011111, utf-16: 100 0001 1111 = 0x041F
+    static_cast<char>(0xd0), static_cast<char>(0x9f),
+    // utf-8: 11010001 10000000, utf-16: 100 0100 0000 = 0x0440
+    static_cast<char>(0xd1), static_cast<char>(0x80),
+    // utf-8: 11010000 10111000, utf-16: 100 0011 1000 = 0x0438
+    static_cast<char>(0xd0), static_cast<char>(0xb8),
+    // utf-8: 11010000 10110010, utf-16: 100 0011 0010 = 0x0432
+    static_cast<char>(0xd0), static_cast<char>(0xb2),
+    // utf-8: 11010000 10110101, utf-16: 100 0011 0101 = 0x0435
+    static_cast<char>(0xd0), static_cast<char>(0xb5),
+    // utf-8: 11010001 10000010, utf-16: 100 0100 0010 = 0x0442
+    static_cast<char>(0xd1), static_cast<char>(0x82), 0
+};
+
+const wchar_t kUtf16Text[] = {
+  L'h', L'i', L' ',
+  L'\x41f', L'\x440', L'\x438', L'\x432', L'\x435', L'\x442', 0
+};
+
+using std::string;
+using std::vector;
+using std::wstring;
+
+class IoWin32Test : public ::testing::Test {
+ public:
+  void SetUp();
+  void TearDown();
+
+ protected:
+  bool CreateAllUnder(wstring path);
+  bool DeleteAllUnder(wstring path);
+
+  WCHAR working_directory[MAX_PATH];
+  string test_tmpdir;
+  wstring wtest_tmpdir;
+};
+
+#define ASSERT_INITIALIZED              \
+  {                                     \
+    EXPECT_FALSE(test_tmpdir.empty());  \
+    EXPECT_FALSE(wtest_tmpdir.empty()); \
+  }
+
+namespace {
+void StripTrailingSlashes(string* str) {
+  int i = str->size() - 1;
+  for (; i >= 0 && ((*str)[i] == '/' || (*str)[i] == '\\'); --i) {}
+  str->resize(i+1);
+}
+
+bool GetEnvVarAsUtf8(const WCHAR* name, string* result) {
+  DWORD size = ::GetEnvironmentVariableW(name, nullptr, 0);
+  if (size > 0 && GetLastError() != ERROR_ENVVAR_NOT_FOUND) {
+    std::unique_ptr<WCHAR[]> wcs(new WCHAR[size]);
+    ::GetEnvironmentVariableW(name, wcs.get(), size);
+    // GetEnvironmentVariableA retrieves an Active-Code-Page-encoded text which
+    // we'd first need to convert to UTF-16 then to UTF-8, because there seems
+    // to be no API function to do that conversion directly.
+    // GetEnvironmentVariableW retrieves an UTF-16-encoded text, which we need
+    // to convert to UTF-8.
+    return strings::wcs_to_utf8(wcs.get(), result);
+  } else {
+    return false;
+  }
+}
+
+bool GetCwdAsUtf8(string* result) {
+  DWORD size = ::GetCurrentDirectoryW(0, nullptr);
+  if (size > 0) {
+    std::unique_ptr<WCHAR[]> wcs(new WCHAR[size]);
+    ::GetCurrentDirectoryW(size, wcs.get());
+    // GetCurrentDirectoryA retrieves an Active-Code-Page-encoded text which
+    // we'd first need to convert to UTF-16 then to UTF-8, because there seems
+    // to be no API function to do that conversion directly.
+    // GetCurrentDirectoryW retrieves an UTF-16-encoded text, which we need
+    // to convert to UTF-8.
+    return strings::wcs_to_utf8(wcs.get(), result);
+  } else {
+    return false;
+  }
+}
+
+bool CreateEmptyFile(const wstring& path) {
+  HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
+                         FILE_ATTRIBUTE_NORMAL, NULL);
+  if (h == INVALID_HANDLE_VALUE) {
+    return false;
+  }
+  CloseHandle(h);
+  return true;
+}
+
+}  // namespace
+
+void IoWin32Test::SetUp() {
+  test_tmpdir.clear();
+  wtest_tmpdir.clear();
+  DWORD size = ::GetCurrentDirectoryW(MAX_PATH, working_directory);
+  EXPECT_GT(size, 0);
+  EXPECT_LT(size, MAX_PATH);
+
+  string tmp;
+  bool ok = false;
+  if (!ok) {
+    // Bazel sets this environment variable when it runs tests.
+    ok = GetEnvVarAsUtf8(L"TEST_TMPDIR", &tmp);
+  }
+  if (!ok) {
+    // Bazel 0.8.0 sets this environment for every build and test action.
+    ok = GetEnvVarAsUtf8(L"TEMP", &tmp);
+  }
+  if (!ok) {
+    // Bazel 0.8.0 sets this environment for every build and test action.
+    ok = GetEnvVarAsUtf8(L"TMP", &tmp);
+  }
+  if (!ok) {
+    // Fall back to using the current directory.
+    ok = GetCwdAsUtf8(&tmp);
+  }
+  if (!ok || tmp.empty()) {
+    FAIL() << "Cannot find a temp directory.";
+  }
+
+  StripTrailingSlashes(&tmp);
+  std::stringstream result;
+  // Deleting files and directories is asynchronous on Windows, and if TearDown
+  // just deleted the previous temp directory, sometimes we cannot recreate the
+  // same directory.
+  // Use a counter so every test method gets its own temp directory.
+  static unsigned int counter = 0;
+  result << tmp << "\\w32tst" << counter++ << ".tmp";
+  test_tmpdir = result.str();
+  wtest_tmpdir = testonly_utf8_to_winpath(test_tmpdir.c_str());
+  ASSERT_FALSE(wtest_tmpdir.empty());
+  ASSERT_TRUE(DeleteAllUnder(wtest_tmpdir));
+  ASSERT_TRUE(CreateAllUnder(wtest_tmpdir));
+}
+
+void IoWin32Test::TearDown() {
+  if (!wtest_tmpdir.empty()) {
+    DeleteAllUnder(wtest_tmpdir);
+  }
+  ::SetCurrentDirectoryW(working_directory);
+}
+
+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(), nullptr) ||
+      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(), nullptr)) {
+        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[path.size() - 1] != L'\\') {
+    path.push_back(L'\\');
+  }
+
+  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, MkdirTestNonAscii) {
+  ASSERT_INITIALIZED;
+
+  // Create a non-ASCII path.
+  // Ensure that we can create the directory using CreateDirectoryW.
+  EXPECT_TRUE(CreateDirectoryW((wtest_tmpdir + L"\\1").c_str(), nullptr));
+  EXPECT_TRUE(CreateDirectoryW((wtest_tmpdir + L"\\1\\" + kUtf16Text).c_str(), nullptr));
+  // Ensure that we can create a very similarly named directory using mkdir.
+  // We don't attemp to delete and recreate the same directory, because on
+  // Windows, deleting files and directories seems to be asynchronous.
+  EXPECT_EQ(mkdir((test_tmpdir + "\\2").c_str(), 0644), 0);
+  EXPECT_EQ(mkdir((test_tmpdir + "\\2\\" + kUtf8Text).c_str(), 0644), 0);
+}
+
+TEST_F(IoWin32Test, ChdirTest) {
+  string path("C:\\");
+  EXPECT_EQ(access(path.c_str(), F_OK), 0);
+  ASSERT_EQ(chdir(path.c_str()), 0);
+
+  // 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, ChdirTestNonAscii) {
+  ASSERT_INITIALIZED;
+
+  // Create a directory with a non-ASCII path and ensure we can cd into it.
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  string nonAscii;
+  EXPECT_TRUE(strings::wcs_to_utf8(wNonAscii.c_str(), &nonAscii));
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  WCHAR cwd[MAX_PATH];
+  EXPECT_TRUE(GetCurrentDirectoryW(MAX_PATH, cwd));
+  // Ensure that we can cd into the path using SetCurrentDirectoryW.
+  EXPECT_TRUE(SetCurrentDirectoryW(wNonAscii.c_str()));
+  EXPECT_TRUE(SetCurrentDirectoryW(cwd));
+  // Ensure that we can cd into the path using chdir.
+  ASSERT_EQ(chdir(nonAscii.c_str()), 0);
+  // Ensure that the GetCurrentDirectoryW returns the desired path.
+  EXPECT_TRUE(GetCurrentDirectoryW(MAX_PATH, cwd));
+  ASSERT_EQ(wNonAscii, cwd);
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsInRelativePathTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_b.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\bar.proto"));
+  // `cd` into `wtest_tmpdir`.
+  EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str()));
+
+  int found_a = 0;
+  int found_b = 0;
+  vector<string> found_bad;
+  // Assert matching a relative path pattern. Results should also be relative.
+  int result = 
+      expand_wildcards(
+          string(kUtf8Text) + "\\foo*.proto",
+          [&found_a, &found_b, &found_bad](const string& p) {
+            if (p == string(kUtf8Text) + "\\foo_a.proto") {
+              found_a++;
+            } else if (p == string(kUtf8Text) + "\\foo_b.proto") {
+              found_b++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  EXPECT_EQ(found_b, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+
+  // Assert matching the exact filename.
+  found_a = 0;
+  found_bad.clear();
+  result = 
+      expand_wildcards(
+          string(kUtf8Text) + "\\foo_a.proto",
+          [&found_a, &found_bad](const string& p) {
+            if (p == string(kUtf8Text) + "\\foo_a.proto") {
+              found_a++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsInAbsolutePathTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_b.proto"));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\bar.proto"));
+
+  int found_a = 0;
+  int found_b = 0;
+  vector<string> found_bad;
+  // Assert matching an absolute path. The results should also use absolute
+  // path.
+  int result = 
+      expand_wildcards(
+          string(test_tmpdir) + "\\" + kUtf8Text + "\\foo*.proto",
+          [this, &found_a, &found_b, &found_bad](const string& p) {
+            if (p == string(this->test_tmpdir)
+                         + "\\"
+                         + kUtf8Text
+                         + "\\foo_a.proto") {
+              found_a++;
+            } else if (p == string(this->test_tmpdir)
+                                + "\\"
+                                + kUtf8Text
+                                + "\\foo_b.proto") {
+              found_b++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  EXPECT_EQ(found_b, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+
+  // Assert matching the exact filename.
+  found_a = 0;
+  found_bad.clear();
+  result = 
+      expand_wildcards(
+          string(test_tmpdir) + "\\" + kUtf8Text + "\\foo_a.proto",
+          [this, &found_a, &found_bad](const string& p) {
+            if (p == string(this->test_tmpdir)
+                         + "\\"
+                         + kUtf8Text
+                         + "\\foo_a.proto") {
+              found_a++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsIgnoresDirectoriesTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  EXPECT_TRUE(CreateDirectoryW((wNonAscii + L"\\foo_b.proto").c_str(), nullptr));
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_c.proto"));
+  // `cd` into `wtest_tmpdir`.
+  EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str()));
+
+  int found_a = 0;
+  int found_c = 0;
+  vector<string> found_bad;
+  // Assert that the pattern matches exactly the expected files, and using the
+  // absolute path as did the input pattern.
+  int result = 
+      expand_wildcards(
+          string(kUtf8Text) + "\\foo*.proto",
+          [&found_a, &found_c, &found_bad](const string& p) {
+            if (p == string(kUtf8Text) + "\\foo_a.proto") {
+              found_a++;
+            } else if (p == string(kUtf8Text) + "\\foo_c.proto") {
+              found_c++;
+            } else {
+              found_bad.push_back(p);
+            }
+          });
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+  EXPECT_EQ(found_a, 1);
+  EXPECT_EQ(found_c, 1);
+  if (!found_bad.empty()) {
+    FAIL() << found_bad[0];
+  }
+}
+
+TEST_F(IoWin32Test, ExpandWildcardsFailsIfNoFileMatchesTest) {
+  wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text);
+  EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
+  // Create mock files we will test pattern matching on.
+  EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto"));
+  // `cd` into `wtest_tmpdir`.
+  EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str()));
+
+  // Control test: should match foo*.proto
+  int result = expand_wildcards(
+      string(kUtf8Text) + "\\foo*.proto", [](const string&) {});
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+
+  // Control test: should match foo_a.proto
+  result = expand_wildcards(
+      string(kUtf8Text) + "\\foo_a.proto", [](const string&) {});
+  EXPECT_EQ(result, ExpandWildcardsResult::kSuccess);
+
+  // Actual test: should not match anything.
+  int result = expand_wildcards(
+      string(kUtf8Text) + "\\bar*.proto", [](const string&) {});
+  ASSERT_EQ(result, ExpandWildcardsResult::kErrorNoMatchingFile);
+}
+
+TEST_F(IoWin32Test, AsWindowsPathTest) {
+  DWORD size = GetCurrentDirectoryW(0, nullptr);
+  std::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_utf8_to_winpath("relative_mkdirtest"),
+            cwd + L"\\relative_mkdirtest");
+  ASSERT_EQ(testonly_utf8_to_winpath("preserve//\\trailing///"),
+            cwd + L"\\preserve\\trailing\\");
+  ASSERT_EQ(testonly_utf8_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_utf8_to_winpath(relpath.str().c_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_utf8_to_winpath("/absolute/unix/path"), L"");
+  // Though valid on Windows, we also don't support UNC paths (\\UNC\\blah).
+  ASSERT_EQ(testonly_utf8_to_winpath("\\driveless\\absolute"), L"");
+  // Though valid in cmd.exe, drive-relative paths are not supported.
+  ASSERT_EQ(testonly_utf8_to_winpath("c:foo"), L"");
+  ASSERT_EQ(testonly_utf8_to_winpath("c:/foo"), L"\\\\?\\c:\\foo");
+  ASSERT_EQ(testonly_utf8_to_winpath("\\\\?\\C:\\foo"), L"\\\\?\\C:\\foo");
+}
+
+TEST_F(IoWin32Test, Utf8Utf16ConversionTest) {
+  string mbs;
+  wstring wcs;
+  ASSERT_TRUE(strings::utf8_to_wcs(kUtf8Text, &wcs));
+  ASSERT_TRUE(strings::wcs_to_utf8(kUtf16Text, &mbs));
+  ASSERT_EQ(wcs, kUtf16Text);
+  ASSERT_EQ(mbs, kUtf8Text);
+}
+
+}  // namespace
+}  // namespace win32
+}  // namespace internal
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // defined(_WIN32)
+