Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
protobuf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
protobuf
Commits
414a625a
Commit
414a625a
authored
Aug 14, 2018
by
Laszlo Csomor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Windows: expand path names on command line
Fixes
https://github.com/google/protobuf/issues/3957
parent
fe2eef4b
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1615 additions
and
21 deletions
+1615
-21
BUILD
BUILD
+1
-19
command_line_interface.cc
src/google/protobuf/compiler/command_line_interface.cc
+25
-0
io_win32.cc
src/google/protobuf/stubs/io_win32.cc
+91
-0
io_win32.cc~
src/google/protobuf/stubs/io_win32.cc~
+505
-0
io_win32.h
src/google/protobuf/stubs/io_win32.h
+25
-0
io_win32.h~
src/google/protobuf/stubs/io_win32.h~
+140
-0
io_win32_unittest.cc
src/google/protobuf/stubs/io_win32_unittest.cc
+189
-2
io_win32_unittest.cc~
src/google/protobuf/stubs/io_win32_unittest.cc~
+639
-0
No files found.
BUILD
View file @
414a625a
...
...
@@ -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"],
)
...
...
src/google/protobuf/compiler/command_line_interface.cc
View file @
414a625a
...
...
@@ -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
())
{
...
...
src/google/protobuf/stubs/io_win32.cc
View file @
414a625a
...
...
@@ -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
)
{
...
...
src/google/protobuf/stubs/io_win32.cc~
0 → 100644
View file @
414a625a
// 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)
src/google/protobuf/stubs/io_win32.h
View file @
414a625a
...
...
@@ -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.
...
...
src/google/protobuf/stubs/io_win32.h~
0 → 100644
View file @
414a625a
// 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__
src/google/protobuf/stubs/io_win32_unittest.cc
View file @
414a625a
...
...
@@ -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
SetCurrent
DirectoryW.
// Ensure that we can create the directory using
Create
DirectoryW.
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
]);
...
...
src/google/protobuf/stubs/io_win32_unittest.cc~
0 → 100644
View file @
414a625a
// 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)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment