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/2891pull/2969/head
parent
9ab7c73f7c
commit
e05e777d46
19 changed files with 970 additions and 81 deletions
@ -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)
|
||||||
|
|
@ -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__
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in new issue