diff options
Diffstat (limited to 'src/google/protobuf/stubs')
-rw-r--r-- | src/google/protobuf/stubs/io_win32.cc | 362 | ||||
-rw-r--r-- | src/google/protobuf/stubs/io_win32.h | 96 | ||||
-rw-r--r-- | src/google/protobuf/stubs/io_win32_unittest.cc | 367 |
3 files changed, 825 insertions, 0 deletions
diff --git a/src/google/protobuf/stubs/io_win32.cc b/src/google/protobuf/stubs/io_win32.cc new file mode 100644 index 00000000..6f0295e1 --- /dev/null +++ b/src/google/protobuf/stubs/io_win32.cc @@ -0,0 +1,362 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: laszlocsomor@google.com (Laszlo Csomor) +// +// Implementation for long-path-aware open/mkdir/etc. on Windows. +// +// These functions convert the input path to an absolute Windows path +// with "\\?\" prefix if necessary, then pass that to _wopen/_wmkdir/etc. +// (declared in <io.h>) respectively. This allows working with files/directories +// whose paths are longer than MAX_PATH (260 chars). +// +// This file is only used on Windows, it's empty on other platforms. + +#if defined(_WIN32) + +// Comment this out to fall back to using the ANSI versions (open, mkdir, ...) +// instead of the Unicode ones (_wopen, _wmkdir, ...). Doing so can be useful to +// debug failing tests if that's caused by the long path support. +#define SUPPORT_LONGPATHS + +#include <ctype.h> +#include <direct.h> +#include <errno.h> +#include <fcntl.h> +#include <io.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wctype.h> +#include <windows.h> + +#include <google/protobuf/stubs/io_win32.h> + +#include <cassert> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +namespace google { +namespace protobuf { +namespace internal { +namespace win32 { +namespace { + +using std::string; +using std::unique_ptr; +using std::wstring; + +template <typename char_type> +struct CharTraits { + static bool is_alpha(char_type ch); +}; + +template <> +struct CharTraits<char> { + static bool is_alpha(char ch) { return isalpha(ch); } +}; + +template <> +struct CharTraits<wchar_t> { + static bool is_alpha(wchar_t ch) { return iswalpha(ch); } +}; + +// Returns true if the path starts with a drive letter, e.g. "c:". +// Note that this won't check for the "\" after the drive letter, so this also +// returns true for "c:foo" (which is "c:\${PWD}\foo"). +// This check requires that a path not have a longpath prefix ("\\?\"). +template <typename char_type> +bool has_drive_letter(const char_type* ch) { + return CharTraits<char_type>::is_alpha(ch[0]) && ch[1] == ':'; +} + +// Returns true if the path starts with a longpath prefix ("\\?\"). +template <typename char_type> +bool has_longpath_prefix(const char_type* path) { + return path[0] == '\\' && path[1] == '\\' && path[2] == '?' && + path[3] == '\\'; +} + +// Returns true if the path starts with a drive specifier (e.g. "c:\"). +template <typename char_type> +bool is_path_absolute(const char_type* path) { + return has_drive_letter(path) && is_separator(path[2]); +} + +template <typename char_type> +bool is_separator(char_type c) { + return c == '/' || c == '\\'; +} + +template <typename char_type> +bool is_drive_relative(const char_type* path) { + return has_drive_letter(path) && (path[2] == 0 || !is_separator(path[2])); +} + +template <typename char_type> +void replace_directory_separators(char_type* p) { + for (; *p; ++p) { + if (*p == '/') { + *p = '\\'; + } + } +} + +string join_paths(const string& path1, const string& path2) { + if (path1.empty() || is_path_absolute(path2.c_str()) || + has_longpath_prefix(path2.c_str())) { + return path2; + } + if (path2.empty()) { + return path1; + } + + if (is_separator(path1.back())) { + return is_separator(path2.front()) ? (path1 + path2.substr(1)) + : (path1 + path2); + } else { + return is_separator(path2.front()) ? (path1 + path2) + : (path1 + '\\' + path2); + } +} + +string normalize(string path) { + if (has_longpath_prefix(path.c_str())) { + path = path.substr(4); + } + + static const string dot("."); + static const string dotdot(".."); + + std::vector<string> segments; + int segment_start = -1; + // Find the path segments in `path` (separated by "/"). + for (int i = 0;; ++i) { + if (!is_separator(path[i]) && path[i] != '\0') { + // The current character does not end a segment, so start one unless it's + // already started. + if (segment_start < 0) { + segment_start = i; + } + } else if (segment_start >= 0 && i > segment_start) { + // The current character is "/" or "\0", so this ends a segment. + // Add that to `segments` if there's anything to add; handle "." and "..". + string segment(path, segment_start, i - segment_start); + segment_start = -1; + if (segment == dotdot) { + if (!segments.empty() && + (!has_drive_letter(segments[0].c_str()) || segments.size() > 1)) { + segments.pop_back(); + } + } else if (segment != dot && !segment.empty()) { + segments.push_back(segment); + } + } + if (path[i] == '\0') { + break; + } + } + + // Handle the case when `path` is just a drive specifier (or some degenerate + // form of it, e.g. "c:\.."). + if (segments.size() == 1 && segments[0].size() == 2 && + has_drive_letter(segments[0].c_str())) { + return segments[0] + '\\'; + } + + // Join all segments. + bool first = true; + std::ostringstream result; + for (const auto& s : segments) { + if (!first) { + result << '\\'; + } + first = false; + result << s; + } + // Preserve trailing separator if the input contained it. + if (is_separator(path.back())) { + result << '\\'; + } + return result.str(); +} + +std::unique_ptr<WCHAR[]> as_wstring(const string& s) { + int len = ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s.size(), NULL, 0); + std::unique_ptr<WCHAR[]> result(new WCHAR[len + 1]); + ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s.size(), result.get(), len + 1); + result.get()[len] = 0; + return std::move(result); +} + +wstring as_wchar_path(const string& path) { + std::unique_ptr<WCHAR[]> wbuf(as_wstring(path)); + replace_directory_separators(wbuf.get()); + return wstring(wbuf.get()); +} + +bool as_windows_path(const string& path, wstring* result) { + if (path.empty()) { + result->clear(); + return true; + } + if (is_separator(path[0]) || is_drive_relative(path.c_str())) { + return false; + } + + string mutable_path = path; + if (!is_path_absolute(mutable_path.c_str()) && + !has_longpath_prefix(mutable_path.c_str())) { + char cwd[MAX_PATH]; + ::GetCurrentDirectoryA(MAX_PATH, cwd); + mutable_path = join_paths(cwd, mutable_path); + } + *result = as_wchar_path(normalize(mutable_path)); + if (!has_longpath_prefix(result->c_str())) { + // Add the "\\?\" prefix unconditionally. This way we prevent the Win32 API + // from processing the path and "helpfully" removing trailing dots from the + // path, for example. + // See https://github.com/bazelbuild/bazel/issues/2935 + *result = wstring(L"\\\\?\\") + *result; + } + return true; +} + +} // namespace + +int open(const char* path, int flags, int mode) { +#ifdef SUPPORT_LONGPATHS + wstring wpath; + if (!as_windows_path(path, &wpath)) { + errno = ENOENT; + return -1; + } + return ::_wopen(wpath.c_str(), flags, mode); +#else + return ::_open(path, flags, mode); +#endif +} + +int mkdir(const char* path, int _mode) { +#ifdef SUPPORT_LONGPATHS + wstring wpath; + if (!as_windows_path(path, &wpath)) { + errno = ENOENT; + return -1; + } + return ::_wmkdir(wpath.c_str()); +#else // not SUPPORT_LONGPATHS + return ::_mkdir(path); +#endif // not SUPPORT_LONGPATHS +} + +int access(const char* path, int mode) { +#ifdef SUPPORT_LONGPATHS + wstring wpath; + if (!as_windows_path(path, &wpath)) { + errno = ENOENT; + return -1; + } + return ::_waccess(wpath.c_str(), mode); +#else + return ::_access(path, mode); +#endif +} + +int chdir(const char* path) { +#ifdef SUPPORT_LONGPATHS + wstring wpath; + if (!as_windows_path(path, &wpath)) { + errno = ENOENT; + return -1; + } + return ::_wchdir(wpath.c_str()); +#else + return ::_chdir(path); +#endif +} + +int stat(const char* path, struct _stat* buffer) { +#ifdef SUPPORT_LONGPATHS + wstring wpath; + if (!as_windows_path(path, &wpath)) { + errno = ENOENT; + return -1; + } + return ::_wstat(wpath.c_str(), buffer); +#else // not SUPPORT_LONGPATHS + return ::_stat(path, buffer); +#endif // not SUPPORT_LONGPATHS +} + +FILE* fopen(const char* path, const char* mode) { +#ifdef SUPPORT_LONGPATHS + wstring wpath; + if (!as_windows_path(path, &wpath)) { + errno = ENOENT; + return NULL; + } + std::unique_ptr<WCHAR[]> wmode(as_wstring(mode)); + return ::_wfopen(wpath.c_str(), wmode.get()); +#else + return ::fopen(path, mode); +#endif +} + +int close(int fd) { return ::close(fd); } + +int dup(int fd) { return ::_dup(fd); } + +int dup2(int fd1, int fd2) { return ::_dup2(fd1, fd2); } + +int read(int fd, void* buffer, size_t size) { + return ::_read(fd, buffer, size); +} + +int setmode(int fd, int mode) { return ::_setmode(fd, mode); } + +int write(int fd, const void* buffer, size_t size) { + return ::_write(fd, buffer, size); +} + +wstring testonly_path_to_winpath(const string& path) { + wstring wpath; + as_windows_path(path, &wpath); + return wpath; +} + +} // namespace win32 +} // namespace internal +} // namespace protobuf +} // namespace google + +#endif // defined(_WIN32) + diff --git a/src/google/protobuf/stubs/io_win32.h b/src/google/protobuf/stubs/io_win32.h new file mode 100644 index 00000000..daccf16c --- /dev/null +++ b/src/google/protobuf/stubs/io_win32.h @@ -0,0 +1,96 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: laszlocsomor@google.com (Laszlo Csomor) +// +// This file contains the declarations for Windows implementations of +// commonly used POSIX functions such as open(2) and access(2), as well +// as macro definitions for flags of these functions. +// +// By including this file you'll redefine open/access/etc. to +// ::google::protobuf::internal::win32::{open/access/etc.}. +// Make sure you don't include a header that attempts to redeclare or +// redefine these functions, that'll lead to confusing compilation +// errors. It's best to #include this file as the last one to ensure that. +// +// This file is only used on Windows, it's empty on other platforms. + +#ifndef GOOGLE_PROTOBUF_STUBS_IO_WIN32_H__ +#define GOOGLE_PROTOBUF_STUBS_IO_WIN32_H__ + +#if defined(_WIN32) + +#include <string> + +namespace google { +namespace protobuf { +namespace internal { +namespace win32 { + +FILE* fopen(const char* path, const char* mode); +int access(const char* path, int mode); +int chdir(const char* path); +int close(int fd); +int dup(int fd); +int dup2(int fd1, int fd2); +int mkdir(const char* path, int _mode); +int open(const char* path, int flags, int mode = 0); +int read(int fd, void* buffer, size_t size); +int setmode(int fd, int mode); +int stat(const char* path, struct _stat* buffer); +int write(int fd, const void* buffer, size_t size); +std::wstring testonly_path_to_winpath(const std::string& path); + +} // namespace win32 +} // namespace internal +} // namespace protobuf +} // namespace google + +#ifndef W_OK +#define W_OK 02 // not defined by MSVC for whatever reason +#endif + +#ifndef F_OK +#define F_OK 00 // not defined by MSVC for whatever reason +#endif + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +#endif // defined(_WIN32) + +#endif // GOOGLE_PROTOBUF_STUBS_IO_WIN32_H__ + + diff --git a/src/google/protobuf/stubs/io_win32_unittest.cc b/src/google/protobuf/stubs/io_win32_unittest.cc new file mode 100644 index 00000000..90bd9c96 --- /dev/null +++ b/src/google/protobuf/stubs/io_win32_unittest.cc @@ -0,0 +1,367 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: laszlocsomor@google.com (Laszlo Csomor) +// +// Unit tests for long-path-aware open/mkdir/access on Windows. +// +// This file is only used on Windows, it's empty on other platforms. + +#if defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wchar.h> +#include <windows.h> + +#include <google/protobuf/stubs/io_win32.h> +#include <google/protobuf/testing/googletest.h> +#include <gtest/gtest.h> + +#include <memory> +#include <sstream> +#include <string> + +namespace google { +namespace protobuf { +namespace internal { +namespace win32 { +namespace { + +using std::string; +using std::unique_ptr; +using std::wstring; + +class IoWin32Test : public ::testing::Test { + public: + void SetUp() override; + void TearDown() override; + + protected: + bool CreateAllUnder(wstring path); + bool DeleteAllUnder(wstring path); + + string test_tmpdir; + wstring wtest_tmpdir; +}; + +#define ASSERT_INITIALIZED \ + { \ + EXPECT_FALSE(test_tmpdir.empty()); \ + EXPECT_FALSE(wtest_tmpdir.empty()); \ + } + +void IoWin32Test::SetUp() { + test_tmpdir = string(TestTempDir()); + wtest_tmpdir.clear(); + if (test_tmpdir.empty()) { + const char* test_tmpdir_env = getenv("TEST_TMPDIR"); + if (test_tmpdir_env != nullptr && *test_tmpdir_env) { + test_tmpdir = string(test_tmpdir_env); + } + + // Only Bazel defines TEST_TMPDIR, CMake does not, so look for other + // suitable environment variables. + if (test_tmpdir.empty()) { + for (const char* name : {"TEMP", "TMP"}) { + test_tmpdir_env = getenv(name); + if (test_tmpdir_env != nullptr && *test_tmpdir_env) { + test_tmpdir = string(test_tmpdir_env); + break; + } + } + } + + // No other temp directory was found. Use the current director + if (test_tmpdir.empty()) { + char buffer[MAX_PATH]; + // Use GetCurrentDirectoryA instead of GetCurrentDirectoryW, because the + // current working directory must always be shorter than MAX_PATH, even + // with + // "\\?\" prefix (except on Windows 10 version 1607 and beyond, after + // opting in to long paths by default [1]). + // + // [1] https://msdn.microsoft.com/en-us/library/windows/ \ + // desktop/aa365247(v=vs.85).aspx#maxpath + DWORD result = ::GetCurrentDirectoryA(MAX_PATH, buffer); + if (result > 0) { + test_tmpdir = string(buffer); + } else { + // Using assertions in SetUp/TearDown seems to confuse the test + // framework, so just leave the member variables empty in case of + // failure. + GOOGLE_CHECK_OK(false); + return; + } + } + } + + while (test_tmpdir.back() == '/' || test_tmpdir.back() == '\\') { + test_tmpdir.pop_back(); + } + test_tmpdir += "\\io_win32_unittest.tmp"; + + // CreateDirectoryA's limit is 248 chars, see MSDN. + // https://msdn.microsoft.com/en-us/library/windows/ \ + // desktop/aa363855(v=vs.85).aspx + wtest_tmpdir = testonly_path_to_winpath(test_tmpdir); + if (!DeleteAllUnder(wtest_tmpdir) || !CreateAllUnder(wtest_tmpdir)) { + GOOGLE_CHECK_OK(false); + test_tmpdir.clear(); + wtest_tmpdir.clear(); + } +} + +void IoWin32Test::TearDown() { + if (!wtest_tmpdir.empty()) { + DeleteAllUnder(wtest_tmpdir); + } +} + +bool IoWin32Test::CreateAllUnder(wstring path) { + // Prepend UNC prefix if the path doesn't have it already. Don't bother + // checking if the path is shorter than MAX_PATH, let's just do it + // unconditionally. + if (path.find(L"\\\\?\\") != 0) { + path = wstring(L"\\\\?\\") + path; + } + if (::CreateDirectoryW(path.c_str(), NULL) || + GetLastError() == ERROR_ALREADY_EXISTS || + GetLastError() == ERROR_ACCESS_DENIED) { + return true; + } + if (GetLastError() == ERROR_PATH_NOT_FOUND) { + size_t pos = path.find_last_of(L'\\'); + if (pos != wstring::npos) { + wstring parent(path, 0, pos); + if (CreateAllUnder(parent) && CreateDirectoryW(path.c_str(), NULL)) { + return true; + } + } + } + return false; +} + +bool IoWin32Test::DeleteAllUnder(wstring path) { + static const wstring kDot(L"."); + static const wstring kDotDot(L".."); + + // Prepend UNC prefix if the path doesn't have it already. Don't bother + // checking if the path is shorter than MAX_PATH, let's just do it + // unconditionally. + if (path.find(L"\\\\?\\") != 0) { + path = wstring(L"\\\\?\\") + path; + } + // Append "\" if necessary. + if (path.back() != '\\') { + path.push_back('\\'); + } + + WIN32_FIND_DATAW metadata; + HANDLE handle = ::FindFirstFileW((path + L"*").c_str(), &metadata); + if (handle == INVALID_HANDLE_VALUE) { + return true; // directory doesn't exist + } + + bool result = true; + do { + wstring childname = metadata.cFileName; + if (kDot != childname && kDotDot != childname) { + wstring childpath = path + childname; + if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + // If this is not a junction, delete its contents recursively. + // Finally delete this directory/junction too. + if (((metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 && + !DeleteAllUnder(childpath)) || + !::RemoveDirectoryW(childpath.c_str())) { + result = false; + break; + } + } else { + if (!::DeleteFileW(childpath.c_str())) { + result = false; + break; + } + } + } + } while (::FindNextFileW(handle, &metadata)); + ::FindClose(handle); + return result; +} + +TEST_F(IoWin32Test, AccessTest) { + ASSERT_INITIALIZED; + + string path = test_tmpdir; + while (path.size() < MAX_PATH - 30) { + path += "\\accesstest"; + EXPECT_EQ(mkdir(path.c_str(), 0644), 0); + } + string file = path + "\\file.txt"; + int fd = open(file.c_str(), O_CREAT | O_WRONLY, 0644); + if (fd > 0) { + EXPECT_EQ(close(fd), 0); + } else { + EXPECT_TRUE(false); + } + + EXPECT_EQ(access(test_tmpdir.c_str(), F_OK), 0); + EXPECT_EQ(access(path.c_str(), F_OK), 0); + EXPECT_EQ(access(path.c_str(), W_OK), 0); + EXPECT_EQ(access(file.c_str(), F_OK | W_OK), 0); + EXPECT_NE(access((file + ".blah").c_str(), F_OK), 0); + EXPECT_NE(access((file + ".blah").c_str(), W_OK), 0); + + EXPECT_EQ(access(".", F_OK), 0); + EXPECT_EQ(access(".", W_OK), 0); + EXPECT_EQ(access((test_tmpdir + "/accesstest").c_str(), F_OK | W_OK), 0); + ASSERT_EQ(access((test_tmpdir + "/./normalize_me/.././accesstest").c_str(), + F_OK | W_OK), + 0); + EXPECT_NE(access("io_win32_unittest.AccessTest.nonexistent", F_OK), 0); + EXPECT_NE(access("io_win32_unittest.AccessTest.nonexistent", W_OK), 0); + + ASSERT_EQ(access("c:bad", F_OK), -1); + ASSERT_EQ(errno, ENOENT); + ASSERT_EQ(access("/tmp/bad", F_OK), -1); + ASSERT_EQ(errno, ENOENT); + ASSERT_EQ(access("\\bad", F_OK), -1); + ASSERT_EQ(errno, ENOENT); +} + +TEST_F(IoWin32Test, OpenTest) { + ASSERT_INITIALIZED; + + string path = test_tmpdir; + while (path.size() < MAX_PATH) { + path += "\\opentest"; + EXPECT_EQ(mkdir(path.c_str(), 0644), 0); + } + string file = path + "\\file.txt"; + int fd = open(file.c_str(), O_CREAT | O_WRONLY, 0644); + if (fd > 0) { + EXPECT_EQ(write(fd, "hello", 5), 5); + EXPECT_EQ(close(fd), 0); + } else { + EXPECT_TRUE(false); + } + + ASSERT_EQ(open("c:bad.txt", O_CREAT | O_WRONLY, 0644), -1); + ASSERT_EQ(errno, ENOENT); + ASSERT_EQ(open("/tmp/bad.txt", O_CREAT | O_WRONLY, 0644), -1); + ASSERT_EQ(errno, ENOENT); + ASSERT_EQ(open("\\bad.txt", O_CREAT | O_WRONLY, 0644), -1); + ASSERT_EQ(errno, ENOENT); +} + +TEST_F(IoWin32Test, MkdirTest) { + ASSERT_INITIALIZED; + + string path = test_tmpdir; + do { + path += "\\mkdirtest"; + ASSERT_EQ(mkdir(path.c_str(), 0644), 0); + } while (path.size() <= MAX_PATH); + + ASSERT_EQ(mkdir("c:bad", 0644), -1); + ASSERT_EQ(errno, ENOENT); + ASSERT_EQ(mkdir("/tmp/bad", 0644), -1); + ASSERT_EQ(errno, ENOENT); + ASSERT_EQ(mkdir("\\bad", 0644), -1); + ASSERT_EQ(errno, ENOENT); +} + +TEST_F(IoWin32Test, ChdirTest) { + char owd[MAX_PATH]; + EXPECT_GT(::GetCurrentDirectoryA(MAX_PATH, owd), 0); + string path("C:\\"); + EXPECT_EQ(access(path.c_str(), F_OK), 0); + ASSERT_EQ(chdir(path.c_str()), 0); + EXPECT_TRUE(::SetCurrentDirectoryA(owd)); + + // Do not try to chdir into the test_tmpdir, it may already contain directory + // names with trailing dots. + // Instead test here with an obviously dot-trailed path. If the win32_chdir + // function would not convert the path to absolute and prefix with "\\?\" then + // the Win32 API would ignore the trailing dot, but because of the prefixing + // there'll be no path processing done, so we'll actually attempt to chdir + // into "C:\some\path\foo." + path = test_tmpdir + "/foo."; + EXPECT_EQ(mkdir(path.c_str(), 644), 0); + EXPECT_EQ(access(path.c_str(), F_OK), 0); + ASSERT_NE(chdir(path.c_str()), 0); +} + +TEST_F(IoWin32Test, AsWindowsPathTest) { + DWORD size = GetCurrentDirectoryW(0, NULL); + unique_ptr<wchar_t[]> cwd_str(new wchar_t[size]); + EXPECT_GT(GetCurrentDirectoryW(size, cwd_str.get()), 0); + wstring cwd = wstring(L"\\\\?\\") + cwd_str.get(); + + ASSERT_EQ(testonly_path_to_winpath("relative_mkdirtest"), + cwd + L"\\relative_mkdirtest"); + ASSERT_EQ(testonly_path_to_winpath("preserve//\\trailing///"), + cwd + L"\\preserve\\trailing\\"); + ASSERT_EQ(testonly_path_to_winpath("./normalize_me\\/../blah"), + cwd + L"\\blah"); + std::ostringstream relpath; + for (wchar_t* p = cwd_str.get(); *p; ++p) { + if (*p == '/' || *p == '\\') { + relpath << "../"; + } + } + relpath << ".\\/../\\./beyond-toplevel"; + ASSERT_EQ(testonly_path_to_winpath(relpath.str()), + wstring(L"\\\\?\\") + cwd_str.get()[0] + L":\\beyond-toplevel"); + + // Absolute unix paths lack drive letters, driveless absolute windows paths + // do too. Neither can be converted to a drive-specifying absolute Windows + // path. + ASSERT_EQ(testonly_path_to_winpath("/absolute/unix/path"), L""); + // Though valid on Windows, we also don't support UNC paths (\\UNC\\blah). + ASSERT_EQ(testonly_path_to_winpath("\\driveless\\absolute"), L""); + // Though valid in cmd.exe, drive-relative paths are not supported. + ASSERT_EQ(testonly_path_to_winpath("c:foo"), L""); + ASSERT_EQ(testonly_path_to_winpath("c:/foo"), L"\\\\?\\c:\\foo"); +} + +} // namespace +} // namespace win32 +} // namespace internal +} // namespace protobuf +} // namespace google + +#endif // defined(_WIN32) + |