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