aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/cpp/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/cpp/util')
-rw-r--r--src/main/cpp/util/BUILD17
-rw-r--r--src/main/cpp/util/file.cc38
-rw-r--r--src/main/cpp/util/file.h11
-rw-r--r--src/main/cpp/util/file_platform.h26
-rw-r--r--src/main/cpp/util/file_posix.cc26
-rw-r--r--src/main/cpp/util/file_windows.cc385
-rw-r--r--src/main/cpp/util/path.cc64
-rw-r--r--src/main/cpp/util/path.h43
-rw-r--r--src/main/cpp/util/path_platform.h119
-rw-r--r--src/main/cpp/util/path_posix.cc60
-rw-r--r--src/main/cpp/util/path_windows.cc420
11 files changed, 734 insertions, 475 deletions
diff --git a/src/main/cpp/util/BUILD b/src/main/cpp/util/BUILD
index 254b6f8f1f..6f53b4d590 100644
--- a/src/main/cpp/util/BUILD
+++ b/src/main/cpp/util/BUILD
@@ -15,13 +15,15 @@ cc_library(
"file_platform.h",
"md5.h",
"numbers.h",
+ "path.h",
+ "path_platform.h",
"port.h",
],
visibility = ["//visibility:public"],
deps = [
":blaze_exit_code",
":errors",
- ":file",
+ ":filesystem",
":md5",
":numbers",
":port",
@@ -30,18 +32,25 @@ cc_library(
)
cc_library(
- name = "file",
- srcs = ["file.cc"] + select({
+ name = "filesystem",
+ srcs = [
+ "file.cc",
+ "path.cc",
+ ] + select({
"//src/conditions:windows": [
"file_windows.cc",
+ "path_windows.cc",
],
"//conditions:default": [
"file_posix.cc",
+ "path_posix.cc",
],
}),
hdrs = [
"file.h",
"file_platform.h",
+ "path.h",
+ "path_platform.h",
],
visibility = [
":ijar",
@@ -108,7 +117,7 @@ cc_library(
visibility = ["//visibility:public"],
deps = [
":blaze_exit_code",
- ":file",
+ ":filesystem",
":logging",
],
)
diff --git a/src/main/cpp/util/file.cc b/src/main/cpp/util/file.cc
index 3eb614c0dc..041d7798dc 100644
--- a/src/main/cpp/util/file.cc
+++ b/src/main/cpp/util/file.cc
@@ -11,6 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+
+#include "src/main/cpp/util/file.h"
+
#include <limits.h> // PATH_MAX
#include <algorithm>
@@ -19,7 +22,7 @@
#include "src/main/cpp/util/errors.h"
#include "src/main/cpp/util/exit_code.h"
-#include "src/main/cpp/util/file.h"
+#include "src/main/cpp/util/path.h"
#include "src/main/cpp/util/strings.h"
namespace blaze_util {
@@ -85,39 +88,6 @@ bool WriteFile(const std::string &content, const std::string &filename,
return WriteFile(content.c_str(), content.size(), filename, perm);
}
-string Dirname(const string &path) {
- return SplitPath(path).first;
-}
-
-string Basename(const string &path) {
- return SplitPath(path).second;
-}
-
-string JoinPath(const string &path1, const string &path2) {
- if (path1.empty()) {
- // "" + "/bar"
- return path2;
- }
-
- if (path1[path1.size() - 1] == '/') {
- if (path2.find('/') == 0) {
- // foo/ + /bar
- return path1 + path2.substr(1);
- } else {
- // foo/ + bar
- return path1 + path2;
- }
- } else {
- if (path2.find('/') == 0) {
- // foo + /bar
- return path1 + path2;
- } else {
- // foo + bar
- return path1 + "/" + path2;
- }
- }
-}
-
class DirectoryTreeWalker : public DirectoryEntryConsumer {
public:
DirectoryTreeWalker(vector<string> *files,
diff --git a/src/main/cpp/util/file.h b/src/main/cpp/util/file.h
index 4bc165110e..235ec87527 100644
--- a/src/main/cpp/util/file.h
+++ b/src/main/cpp/util/file.h
@@ -63,17 +63,6 @@ bool ReadFrom(file_handle_type handle, void *data, size_t size);
bool WriteFile(const std::string &content, const std::string &filename,
unsigned int perm = 0644);
-// Returns the part of the path before the final "/". If there is a single
-// leading "/" in the path, the result will be the leading "/". If there is
-// no "/" in the path, the result is the empty prefix of the input (i.e., "").
-std::string Dirname(const std::string &path);
-
-// Returns the part of the path after the final "/". If there is no
-// "/" in the path, the result is the same as the input.
-std::string Basename(const std::string &path);
-
-std::string JoinPath(const std::string &path1, const std::string &path2);
-
// Lists all files in `path` and all of its subdirectories.
//
// Does not follow symlinks / junctions.
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index 5b96133d3a..ac4fc3ab00 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -50,9 +50,6 @@ class IFileMtime {
// Creates a platform-specific implementation of `IFileMtime`.
IFileMtime *CreateFileMtime();
-// Split a path to dirname and basename parts.
-std::pair<std::string, std::string> SplitPath(const std::string &path);
-
#if defined(COMPILER_MSVC) || defined(__CYGWIN__)
// We cannot include <windows.h> because it #defines many symbols that conflict
// with our function names, e.g. GetUserName, SendMessage.
@@ -162,17 +159,9 @@ bool CanExecuteFile(const std::string &path);
// Follows symlinks/junctions.
bool CanAccessDirectory(const std::string &path);
-bool IsDevNull(const char *path);
-
// Returns true if `path` refers to a directory or a symlink/junction to one.
bool IsDirectory(const std::string& path);
-// Returns true if `path` is the root directory or a Windows drive root.
-bool IsRootDirectory(const std::string &path);
-
-// Returns true if `path` is absolute.
-bool IsAbsolute(const std::string &path);
-
// Calls fsync() on the file (or directory) specified in 'file_path'.
// pdie() if syncing fails.
void SyncFile(const std::string& path);
@@ -211,20 +200,7 @@ void ForEachDirectoryEntry(const std::string &path,
DirectoryEntryConsumer *consume);
#if defined(COMPILER_MSVC) || defined(__CYGWIN__)
-const wchar_t *RemoveUncPrefixMaybe(const wchar_t *ptr);
-
-bool AsWindowsPath(const std::string &path, std::string *result,
- std::string *error);
-
-bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath,
- std::string *error);
-
-// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
-// Result will never have a UNC prefix, nor a trailing "/" or "\".
-// Works also for non-existent paths; shortens as much of them as it can.
-// Also works for non-existent drives.
-bool AsShortWindowsPath(const std::string &path, std::string *result,
- std::string *error);
+std::wstring GetCwdW();
#endif // defined(COMPILER_MSVC) || defined(__CYGWIN__)
} // namespace blaze_util
diff --git a/src/main/cpp/util/file_posix.cc b/src/main/cpp/util/file_posix.cc
index 5136df0655..df05b54a45 100644
--- a/src/main/cpp/util/file_posix.cc
+++ b/src/main/cpp/util/file_posix.cc
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "src/main/cpp/util/file_platform.h"
+
#include <dirent.h> // DIR, dirent, opendir, closedir
#include <errno.h>
#include <fcntl.h> // O_RDONLY
@@ -29,6 +31,8 @@
#include "src/main/cpp/util/exit_code.h"
#include "src/main/cpp/util/file.h"
#include "src/main/cpp/util/logging.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
#include "src/main/cpp/util/strings.h"
namespace blaze_util {
@@ -178,18 +182,6 @@ IPipe* CreatePipe() {
return new PosixPipe(fd[0], fd[1]);
}
-pair<string, string> SplitPath(const string &path) {
- size_t pos = path.rfind('/');
-
- // Handle the case with no '/' in 'path'.
- if (pos == string::npos) return std::make_pair("", path);
-
- // Handle the case with a single leading '/' in 'path'.
- if (pos == 0) return std::make_pair(string(path, 0, 1), string(path, 1));
-
- return std::make_pair(string(path, 0, pos), string(path, pos + 1));
-}
-
int ReadFromHandle(file_handle_type fd, void *data, size_t size, int *error) {
int result = read(fd, data, size);
if (error != nullptr) {
@@ -299,10 +291,6 @@ static bool CanAccess(const string &path, bool read, bool write, bool exec) {
return access(path.c_str(), mode) == 0;
}
-bool IsDevNull(const char *path) {
- return path != NULL && *path != 0 && strncmp("/dev/null\0", path, 10) == 0;
-}
-
bool CanReadFile(const std::string &path) {
return !IsDirectory(path) && CanAccess(path, true, false, false);
}
@@ -320,12 +308,6 @@ bool IsDirectory(const string& path) {
return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode);
}
-bool IsRootDirectory(const string &path) {
- return path.size() == 1 && path[0] == '/';
-}
-
-bool IsAbsolute(const string &path) { return !path.empty() && path[0] == '/'; }
-
void SyncFile(const string& path) {
const char* file_path = path.c_str();
int fd = open(file_path, O_RDONLY);
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index 39160c9c22..537852eb70 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -24,6 +24,8 @@
#include "src/main/cpp/util/exit_code.h"
#include "src/main/cpp/util/file.h"
#include "src/main/cpp/util/logging.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
#include "src/main/cpp/util/strings.h"
#include "src/main/native/windows/file.h"
#include "src/main/native/windows/util.h"
@@ -40,11 +42,7 @@ using bazel::windows::GetLongPath;
using bazel::windows::HasUncPrefix;
using bazel::windows::OpenDirectory;
-// Returns the current working directory as a Windows path.
-// The result may have a UNC prefix.
-static unique_ptr<WCHAR[]> GetCwdW();
-static char GetCurrentDrive();
// Returns true if `path` refers to a directory or (non-dangling) junction.
// `path` must be a normalized Windows path, with UNC prefix (and absolute) if
@@ -57,64 +55,15 @@ static bool IsDirectoryW(const wstring& path);
// necessary.
static bool UnlinkPathW(const wstring& path);
-static bool IsRootDirectoryW(const wstring& path);
-
static bool MakeDirectoriesW(const wstring& path);
static bool CanReadFileW(const wstring& path);
-// Returns a normalized form of the input `path`.
-//
-// `path` must be a relative or absolute Windows path, it may use "/" instead of
-// "\" but must not be a Unix-style (MSYS) path.
-// The result won't have a UNC prefix, even if `path` did.
-//
-// Normalization means removing "." references, resolving ".." references, and
-// deduplicating "/" characters while converting them to "\".
-// For example if `path` is "foo/../bar/.//qux", the result is "bar\qux".
-//
-// Uplevel references that cannot go any higher in the directory tree are simply
-// ignored, e.g. "c:/.." is normalized to "c:\" and "../../foo" is normalized to
-// "foo".
-//
-// Visible for testing, would be static otherwise.
-string NormalizeWindowsPath(string path);
-
-template <typename char_type>
-struct CharTraits {
- static bool IsAlpha(char_type ch);
-};
-
-template <>
-struct CharTraits<char> {
- static bool IsAlpha(char ch) { return isalpha(ch); }
-};
-
-template <>
-struct CharTraits<wchar_t> {
- static bool IsAlpha(wchar_t ch) { return iswalpha(ch); }
-};
-
template <typename char_type>
static bool IsPathSeparator(char_type ch) {
return ch == '/' || ch == '\\';
}
-template <typename char_type>
-static bool HasDriveSpecifierPrefix(const char_type* ch) {
- return CharTraits<char_type>::IsAlpha(ch[0]) && ch[1] == ':';
-}
-
-static void AddUncPrefixMaybe(wstring* path, size_t max_path = MAX_PATH) {
- if (path->size() >= max_path && !HasUncPrefix(path->c_str())) {
- *path = wstring(L"\\\\?\\") + *path;
- }
-}
-
-const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) {
- return ptr + (HasUncPrefix(ptr) ? 4 : 0);
-}
-
class WindowsPipe : public IPipe {
public:
WindowsPipe(const HANDLE& read_handle, const HANDLE& write_handle)
@@ -300,239 +249,6 @@ FILETIME WindowsFileMtime::GetFuture(WORD years) {
IFileMtime* CreateFileMtime() { return new WindowsFileMtime(); }
-// Checks if the path is absolute and/or is a root path.
-//
-// If `must_be_root` is true, then in addition to being absolute, the path must
-// also be just the root part, no other components, e.g. "c:\" is both absolute
-// and root, but "c:\foo" is just absolute.
-template <typename char_type>
-static bool IsRootOrAbsolute(const basic_string<char_type>& path,
- bool must_be_root) {
- // An absolute path is one that starts with "/", "\", "c:/", "c:\",
- // "\\?\c:\", or rarely "\??\c:\" or "\\.\c:\".
- //
- // It is unclear whether the UNC prefix is just "\\?\" or is "\??\" also
- // valid (in some cases it seems to be, though MSDN doesn't mention it).
- return
- // path is (or starts with) "/" or "\"
- ((must_be_root ? path.size() == 1 : !path.empty()) &&
- IsPathSeparator(path[0])) ||
- // path is (or starts with) "c:/" or "c:\" or similar
- ((must_be_root ? path.size() == 3 : path.size() >= 3) &&
- HasDriveSpecifierPrefix(path.c_str()) && IsPathSeparator(path[2])) ||
- // path is (or starts with) "\\?\c:\" or "\??\c:\" or similar
- ((must_be_root ? path.size() == 7 : path.size() >= 7) &&
- HasUncPrefix(path.c_str()) &&
- HasDriveSpecifierPrefix(path.c_str() + 4) && IsPathSeparator(path[6]));
-}
-
-template <typename char_type>
-static pair<basic_string<char_type>, basic_string<char_type> > SplitPathImpl(
- const basic_string<char_type>& path) {
- if (path.empty()) {
- return std::make_pair(basic_string<char_type>(), basic_string<char_type>());
- }
-
- size_t pos = path.size() - 1;
- for (auto it = path.crbegin(); it != path.crend(); ++it, --pos) {
- if (IsPathSeparator(*it)) {
- if ((pos == 2 || pos == 6) &&
- IsRootOrAbsolute(path.substr(0, pos + 1), /* must_be_root */ true)) {
- // Windows path, top-level directory, e.g. "c:\foo",
- // result is ("c:\", "foo").
- // Or UNC path, top-level directory, e.g. "\\?\c:\foo"
- // result is ("\\?\c:\", "foo").
- return std::make_pair(
- // Include the "/" or "\" in the drive specifier.
- path.substr(0, pos + 1), path.substr(pos + 1));
- } else {
- // Windows path (neither top-level nor drive root), Unix path, or
- // relative path.
- return std::make_pair(
- // If the only "/" is the leading one, then that shall be the first
- // pair element, otherwise the substring up to the rightmost "/".
- pos == 0 ? path.substr(0, 1) : path.substr(0, pos),
- // If the rightmost "/" is the tail, then the second pair element
- // should be empty.
- pos == path.size() - 1 ? basic_string<char_type>()
- : path.substr(pos + 1));
- }
- }
- }
- // Handle the case with no '/' or '\' in `path`.
- return std::make_pair(basic_string<char_type>(), path);
-}
-
-pair<string, string> SplitPath(const string& path) {
- return SplitPathImpl(path);
-}
-
-pair<wstring, wstring> SplitPathW(const wstring& path) {
- return SplitPathImpl(path);
-}
-
-bool AsWindowsPath(const string& path, string* result, string* error) {
- if (path.empty()) {
- result->clear();
- return true;
- }
- if (IsDevNull(path.c_str())) {
- result->assign("NUL");
- return true;
- }
- if (HasUncPrefix(path.c_str())) {
- // Path has "\\?\" prefix --> assume it's already Windows-style.
- *result = path.c_str();
- return true;
- }
- if (IsPathSeparator(path[0]) && path.size() > 1 && IsPathSeparator(path[1])) {
- // Unsupported path: "\\" or "\\server\path", or some degenerate form of
- // these, such as "//foo".
- if (error) {
- *error = "network paths are unsupported";
- }
- return false;
- }
- if (HasDriveSpecifierPrefix(path.c_str()) &&
- (path.size() < 3 || !IsPathSeparator(path[2]))) {
- // Unsupported path: "c:" or "c:foo"
- if (error) {
- *error = "working-directory relative paths are unsupported";
- }
- return false;
- }
-
- string mutable_path = path;
- if (path[0] == '/') {
- if (error) {
- *error = "Unix-style paths are unsupported";
- }
- return false;
- }
-
- if (path[0] == '\\') {
- // This is an absolute Windows path on the current drive, e.g. "\foo\bar".
- mutable_path = string(1, GetCurrentDrive()) + ":" + path;
- } // otherwise this is a relative path, or absolute Windows path.
-
- result->assign(NormalizeWindowsPath(mutable_path));
- return true;
-}
-
-// Converts a UTF8-encoded `path` to a normalized, widechar Windows path.
-//
-// Returns true if conversion succeeded and sets the contents of `result` to it.
-//
-// The input `path` may be an absolute or relative Windows path.
-//
-// The returned path is normalized (see NormalizeWindowsPath).
-//
-// If `path` had a "\\?\" prefix then the function assumes it's already Windows
-// style and converts it to wstring without any alterations.
-// Otherwise `path` is normalized and converted to a Windows path and the result
-// won't have a "\\?\" prefix even if it's longer than MAX_PATH (adding the
-// prefix is the caller's responsibility).
-//
-// The method recognizes current-drive-relative Windows paths ("\foo") turning
-// them into absolute paths ("c:\foo").
-bool AsWindowsPath(const string& path, wstring* result, string* error) {
- string normalized_win_path;
- if (!AsWindowsPath(path, &normalized_win_path, error)) {
- return false;
- }
-
- result->assign(CstringToWstring(normalized_win_path.c_str()).get());
- return true;
-}
-
-bool AsAbsoluteWindowsPath(const string& path, wstring* result, string* error) {
- if (path.empty()) {
- result->clear();
- return true;
- }
- if (IsDevNull(path.c_str())) {
- result->assign(L"NUL");
- return true;
- }
- if (!AsWindowsPath(path, result, error)) {
- return false;
- }
- if (!IsRootOrAbsolute(*result, /* must_be_root */ false)) {
- *result = wstring(GetCwdW().get()) + L"\\" + *result;
- }
- if (!HasUncPrefix(result->c_str())) {
- *result = wstring(L"\\\\?\\") + *result;
- }
- return true;
-}
-
-bool AsShortWindowsPath(const string& path, string* result, string* error) {
- if (IsDevNull(path.c_str())) {
- result->assign("NUL");
- return true;
- }
-
- result->clear();
- wstring wpath;
- wstring wsuffix;
- if (!AsAbsoluteWindowsPath(path, &wpath, error)) {
- return false;
- }
- DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
- if (size == 0) {
- // GetShortPathNameW can fail if `wpath` does not exist. This is expected
- // when we are about to create a file at that path, so instead of failing,
- // walk up in the path until we find a prefix that exists and can be
- // shortened, or is a root directory. Save the non-existent tail in
- // `wsuffix`, we'll add it back later.
- std::vector<wstring> segments;
- while (size == 0 && !IsRootDirectoryW(wpath)) {
- pair<wstring, wstring> split = SplitPathW(wpath);
- wpath = split.first;
- segments.push_back(split.second);
- size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
- }
-
- // Join all segments.
- std::wostringstream builder;
- bool first = true;
- for (auto it = segments.crbegin(); it != segments.crend(); ++it) {
- if (!first || !IsRootDirectoryW(wpath)) {
- builder << L'\\' << *it;
- } else {
- builder << *it;
- }
- first = false;
- }
- wsuffix = builder.str();
- }
-
- wstring wresult;
- if (IsRootDirectoryW(wpath)) {
- // Strip the UNC prefix from `wpath`, and the leading "\" from `wsuffix`.
- wresult = wstring(RemoveUncPrefixMaybe(wpath.c_str())) + wsuffix;
- } else {
- unique_ptr<WCHAR[]> wshort(
- new WCHAR[size]); // size includes null-terminator
- if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
- if (error) {
- string last_error = GetLastErrorString();
- std::stringstream msg;
- msg << "AsShortWindowsPath(" << path << "): GetShortPathNameW("
- << blaze_util::WstringToString(wpath) << ") failed: " << last_error;
- *error = msg.str();
- }
- return false;
- }
- // GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
- wresult = wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix;
- }
-
- result->assign(WstringToCstring(wresult.c_str()).get());
- ToLower(result);
- return true;
-}
-
static bool OpenFileForReading(const string& filename, HANDLE* result) {
if (filename.empty()) {
return false;
@@ -1067,14 +783,6 @@ bool CanAccessDirectory(const std::string& path) {
return true;
}
-bool IsDevNull(const char* path) {
- return path != NULL && *path != 0 &&
- (strncmp("/dev/null\0", path, 10) == 0 ||
- ((path[0] == 'N' || path[0] == 'n') &&
- (path[1] == 'U' || path[1] == 'u') &&
- (path[2] == 'L' || path[2] == 'l') && path[3] == 0));
-}
-
static bool IsDirectoryW(const wstring& path) {
DWORD attrs = ::GetFileAttributesW(path.c_str());
return (attrs != INVALID_FILE_ATTRIBUTES) &&
@@ -1097,21 +805,11 @@ bool IsDirectory(const string& path) {
return IsDirectoryW(wpath);
}
-bool IsRootDirectory(const string& path) {
- return IsRootOrAbsolute(path, true);
-}
-
-bool IsAbsolute(const string& path) { return IsRootOrAbsolute(path, false); }
-
void SyncFile(const string& path) {
// No-op on Windows native; unsupported by Cygwin.
// fsync always fails on Cygwin with "Permission denied" for some reason.
}
-static bool IsRootDirectoryW(const wstring& path) {
- return IsRootOrAbsolute(path, true);
-}
-
static bool MakeDirectoriesW(const wstring& path) {
if (path.empty()) {
return false;
@@ -1150,7 +848,7 @@ bool MakeDirectories(const string& path, unsigned int mode) {
return MakeDirectoriesW(wpath);
}
-static unique_ptr<WCHAR[]> GetCwdW() {
+std::wstring GetCwdW() {
DWORD len = ::GetCurrentDirectoryW(0, nullptr);
unique_ptr<WCHAR[]> cwd(new WCHAR[len]);
if (!::GetCurrentDirectoryW(len, cwd.get())) {
@@ -1160,18 +858,12 @@ static unique_ptr<WCHAR[]> GetCwdW() {
for (WCHAR* p = cwd.get(); *p != 0; ++p) {
*p = towlower(*p);
}
- return std::move(cwd);
+ return std::wstring(cwd.get());
}
string GetCwd() {
- return string(WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().get())).get());
-}
-
-static char GetCurrentDrive() {
- unique_ptr<wchar_t[]> cwd = GetCwdW();
- wchar_t wdrive = RemoveUncPrefixMaybe(cwd.get())[0];
- wchar_t offset = wdrive >= L'A' && wdrive <= L'Z' ? L'A' : L'a';
- return 'a' + wdrive - offset;
+ return string(
+ WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().c_str())).get());
}
bool ChangeDirectory(const string& path) {
@@ -1227,69 +919,4 @@ void ForEachDirectoryEntry(const string &path,
::FindClose(handle);
}
-string NormalizeWindowsPath(string path) {
- if (path.empty()) {
- return "";
- }
- if (path[0] == '/') {
- // This is an absolute MSYS path, error out.
- BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
- << "NormalizeWindowsPath(" << path << "): expected a Windows path";
- }
- if (path.size() >= 4 && HasUncPrefix(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 (!IsPathSeparator(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() &&
- !HasDriveSpecifierPrefix(segments[0].c_str())) {
- segments.pop_back();
- }
- } else if (segment != dot) {
- 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 &&
- HasDriveSpecifierPrefix(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;
- }
- return result.str();
-}
-
} // namespace blaze_util
diff --git a/src/main/cpp/util/path.cc b/src/main/cpp/util/path.cc
new file mode 100644
index 0000000000..efa10b81d4
--- /dev/null
+++ b/src/main/cpp/util/path.cc
@@ -0,0 +1,64 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/main/cpp/util/path.h"
+
+#include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path_platform.h"
+
+namespace blaze_util {
+
+std::string Dirname(const std::string &path) { return SplitPath(path).first; }
+
+std::string Basename(const std::string &path) { return SplitPath(path).second; }
+
+std::string JoinPath(const std::string &path1, const std::string &path2) {
+ if (path1.empty()) {
+ // "" + "/bar"
+ return path2;
+ }
+
+ if (path1[path1.size() - 1] == '/') {
+ if (path2.find('/') == 0) {
+ // foo/ + /bar
+ return path1 + path2.substr(1);
+ } else {
+ // foo/ + bar
+ return path1 + path2;
+ }
+ } else {
+ if (path2.find('/') == 0) {
+ // foo + /bar
+ return path1 + path2;
+ } else {
+ // foo + bar
+ return path1 + "/" + path2;
+ }
+ }
+}
+
+std::string MakeAbsolute(const std::string &path) {
+ std::string converted_path = ConvertPath(path);
+ if (converted_path.empty()) {
+ return GetCwd();
+ }
+ if (IsDevNull(converted_path.c_str()) ||
+ blaze_util::IsAbsolute(converted_path)) {
+ return converted_path;
+ }
+
+ return JoinPath(blaze_util::GetCwd(), converted_path);
+}
+
+} // namespace blaze_util
diff --git a/src/main/cpp/util/path.h b/src/main/cpp/util/path.h
new file mode 100644
index 0000000000..38e735b7af
--- /dev/null
+++ b/src/main/cpp/util/path.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef BAZEL_SRC_MAIN_CPP_UTIL_PATH_H_
+#define BAZEL_SRC_MAIN_CPP_UTIL_PATH_H_
+
+#include <string>
+
+namespace blaze_util {
+
+// Returns the part of the path before the final "/". If there is a single
+// leading "/" in the path, the result will be the leading "/". If there is
+// no "/" in the path, the result is the empty prefix of the input (i.e., "").
+std::string Dirname(const std::string &path);
+
+// Returns the part of the path after the final "/". If there is no
+// "/" in the path, the result is the same as the input.
+std::string Basename(const std::string &path);
+
+std::string JoinPath(const std::string &path1, const std::string &path2);
+
+// Returns the given path in absolute form. Does not change paths that are
+// already absolute.
+//
+// If called from working directory "/bar":
+// MakeAbsolute("foo") --> "/bar/foo"
+// MakeAbsolute("/foo") ---> "/foo"
+// MakeAbsolute("C:/foo") ---> "C:/foo"
+std::string MakeAbsolute(const std::string &path);
+
+} // namespace blaze_util
+
+#endif // BAZEL_SRC_MAIN_CPP_UTIL_PATH_H_
diff --git a/src/main/cpp/util/path_platform.h b/src/main/cpp/util/path_platform.h
new file mode 100644
index 0000000000..abb25dcb75
--- /dev/null
+++ b/src/main/cpp/util/path_platform.h
@@ -0,0 +1,119 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef BAZEL_SRC_MAIN_CPP_UTIL_PATH_PLATFORM_H_
+#define BAZEL_SRC_MAIN_CPP_UTIL_PATH_PLATFORM_H_
+
+#include <string>
+
+namespace blaze_util {
+
+// Convert a path from Bazel internal form to underlying OS form.
+// On Unixes this is an identity operation.
+// On Windows, Bazel internal form is cygwin path, and underlying OS form
+// is Windows path.
+std::string ConvertPath(const std::string &path);
+
+// Converts `path` to a string that's safe to pass as path in a JVM flag.
+// See https://github.com/bazelbuild/bazel/issues/2576
+std::string PathAsJvmFlag(const std::string &path);
+
+// Compares two absolute paths. Necessary because the same path can have
+// multiple different names under msys2: "C:\foo\bar" or "C:/foo/bar"
+// (Windows-style) and "/c/foo/bar" (msys2 style). Returns if the paths are
+// equal.
+bool CompareAbsolutePaths(const std::string &a, const std::string &b);
+
+// Split a path to dirname and basename parts.
+std::pair<std::string, std::string> SplitPath(const std::string &path);
+
+bool IsDevNull(const char *path);
+
+// Returns true if `path` is the root directory or a Windows drive root.
+bool IsRootDirectory(const std::string &path);
+
+// Returns true if `path` is absolute.
+bool IsAbsolute(const std::string &path);
+
+// TODO(bazel-team) consider changing the path(_platform) header split to be a
+// path.h and path_windows.h split, which would make it clearer what functions
+// are included by an import statement. The downside to this gain in clarity
+// is that this would add more complexity to the implementation file(s)? of
+// path.h, which would have to have the platform-specific implementations.
+#if defined(COMPILER_MSVC) || defined(__CYGWIN__)
+const wchar_t *RemoveUncPrefixMaybe(const wchar_t *ptr);
+void AddUncPrefixMaybe(std::wstring *path);
+
+std::pair<std::wstring, std::wstring> SplitPathW(const std::wstring &path);
+
+bool IsRootDirectoryW(const std::wstring &path);
+
+bool AsWindowsPath(const std::string &path, std::string *result,
+ std::string *error);
+
+// Returns a normalized form of the input `path`.
+//
+// `path` must be a relative or absolute Windows path, it may use "/" instead of
+// "\" but must not be a Unix-style (MSYS) path.
+// The result won't have a UNC prefix, even if `path` did.
+//
+// Normalization means removing "." references, resolving ".." references, and
+// deduplicating "/" characters while converting them to "\".
+// For example if `path` is "foo/../bar/.//qux", the result is "bar\qux".
+//
+// Uplevel references that cannot go any higher in the directory tree are simply
+// ignored, e.g. "c:/.." is normalized to "c:\" and "../../foo" is normalized to
+// "foo".
+//
+// Visible for testing, would be static otherwise.
+std::string NormalizeWindowsPath(std::string path);
+
+// Converts a UTF8-encoded `path` to a normalized, widechar Windows path.
+//
+// Returns true if conversion succeeded and sets the contents of `result` to it.
+//
+// The input `path` may be an absolute or relative Windows path.
+//
+// The returned path is normalized (see NormalizeWindowsPath).
+//
+// If `path` had a "\\?\" prefix then the function assumes it's already Windows
+// style and converts it to wstring without any alterations.
+// Otherwise `path` is normalized and converted to a Windows path and the result
+// won't have a "\\?\" prefix even if it's longer than MAX_PATH (adding the
+// prefix is the caller's responsibility).
+//
+// The method recognizes current-drive-relative Windows paths ("\foo") turning
+// them into absolute paths ("c:\foo").
+bool AsWindowsPath(const std::string &path, std::wstring *result,
+ std::string *error);
+
+bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath,
+ std::string *error);
+
+// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
+// Result will never have a UNC prefix, nor a trailing "/" or "\".
+// Works also for non-existent paths; shortens as much of them as it can.
+// Also works for non-existent drives.
+bool AsShortWindowsPath(const std::string &path, std::string *result,
+ std::string *error);
+
+template <typename char_type>
+bool IsPathSeparator(char_type ch);
+
+template <typename char_type>
+bool HasDriveSpecifierPrefix(const char_type *ch);
+
+#endif // defined(COMPILER_MSVC) || defined(__CYGWIN__)
+} // namespace blaze_util
+
+#endif // BAZEL_SRC_MAIN_CPP_UTIL_PATH_PLATFORM_H_
diff --git a/src/main/cpp/util/path_posix.cc b/src/main/cpp/util/path_posix.cc
new file mode 100644
index 0000000000..bbeffca30c
--- /dev/null
+++ b/src/main/cpp/util/path_posix.cc
@@ -0,0 +1,60 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/main/cpp/util/path_platform.h"
+
+#include <limits.h> // PATH_MAX
+
+#include <string.h> // strncmp
+#include <unistd.h> // access, open, close, fsync
+#include "src/main/cpp/util/errors.h"
+#include "src/main/cpp/util/exit_code.h"
+#include "src/main/cpp/util/logging.h"
+
+namespace blaze_util {
+
+std::string ConvertPath(const std::string &path) { return path; }
+
+std::string PathAsJvmFlag(const std::string &path) { return path; }
+
+bool CompareAbsolutePaths(const std::string &a, const std::string &b) {
+ return a == b;
+}
+
+std::pair<std::string, std::string> SplitPath(const std::string &path) {
+ size_t pos = path.rfind('/');
+
+ // Handle the case with no '/' in 'path'.
+ if (pos == std::string::npos) return std::make_pair("", path);
+
+ // Handle the case with a single leading '/' in 'path'.
+ if (pos == 0)
+ return std::make_pair(std::string(path, 0, 1), std::string(path, 1));
+
+ return std::make_pair(std::string(path, 0, pos), std::string(path, pos + 1));
+}
+
+bool IsDevNull(const char *path) {
+ return path != NULL && *path != 0 && strncmp("/dev/null\0", path, 10) == 0;
+}
+
+bool IsRootDirectory(const std::string &path) {
+ return path.size() == 1 && path[0] == '/';
+}
+
+bool IsAbsolute(const std::string &path) {
+ return !path.empty() && path[0] == '/';
+}
+
+} // namespace blaze_util
diff --git a/src/main/cpp/util/path_windows.cc b/src/main/cpp/util/path_windows.cc
new file mode 100644
index 0000000000..d3756c71bf
--- /dev/null
+++ b/src/main/cpp/util/path_windows.cc
@@ -0,0 +1,420 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/main/cpp/util/path_platform.h"
+
+#include <wchar.h> // wcslen
+#include <windows.h>
+
+#include <algorithm>
+#include <memory> // unique_ptr
+#include <sstream>
+#include <vector>
+
+#include "src/main/cpp/util/errors.h"
+#include "src/main/cpp/util/exit_code.h"
+#include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/logging.h"
+#include "src/main/cpp/util/strings.h"
+#include "src/main/native/windows/file.h"
+
+namespace blaze_util {
+
+using bazel::windows::HasUncPrefix;
+
+static char GetCurrentDrive();
+
+template <typename char_type>
+struct CharTraits {
+ static bool IsAlpha(char_type ch);
+};
+
+template <>
+struct CharTraits<char> {
+ static bool IsAlpha(char ch) { return isalpha(ch); }
+};
+
+template <>
+struct CharTraits<wchar_t> {
+ static bool IsAlpha(wchar_t ch) { return iswalpha(ch); }
+};
+
+template <typename char_type>
+static bool IsPathSeparator(char_type ch) {
+ return ch == '/' || ch == '\\';
+}
+
+template <typename char_type>
+static bool HasDriveSpecifierPrefix(const char_type* ch) {
+ return CharTraits<char_type>::IsAlpha(ch[0]) && ch[1] == ':';
+}
+
+std::string ConvertPath(const std::string& path) {
+ // The path may not be Windows-style and may not be normalized, so convert it.
+ std::wstring wpath;
+ std::string error;
+ if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
+ BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+ << "ConvertPath(" << path
+ << "): AsAbsoluteWindowsPath failed: " << error;
+ }
+ std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower);
+ return std::string(
+ WstringToCstring(RemoveUncPrefixMaybe(wpath.c_str())).get());
+}
+
+bool CompareAbsolutePaths(const std::string& a, const std::string& b) {
+ return ConvertPath(a) == ConvertPath(b);
+}
+
+std::string PathAsJvmFlag(const std::string& path) {
+ std::string spath;
+ std::string error;
+ if (!AsShortWindowsPath(path, &spath, &error)) {
+ BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+ << "PathAsJvmFlag(" << path
+ << "): AsShortWindowsPath failed: " << error;
+ }
+ // Convert backslashes to forward slashes, in order to avoid the JVM parsing
+ // Windows paths as if they contained escaped characters.
+ // See https://github.com/bazelbuild/bazel/issues/2576
+ std::replace(spath.begin(), spath.end(), '\\', '/');
+ return spath;
+}
+
+void AddUncPrefixMaybe(std::wstring* path) {
+ if (path->size() >= MAX_PATH && !HasUncPrefix(path->c_str())) {
+ *path = std::wstring(L"\\\\?\\") + *path;
+ }
+}
+
+const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) {
+ return ptr + (HasUncPrefix(ptr) ? 4 : 0);
+}
+
+// Checks if the path is absolute and/or is a root path.
+//
+// If `must_be_root` is true, then in addition to being absolute, the path must
+// also be just the root part, no other components, e.g. "c:\" is both absolute
+// and root, but "c:\foo" is just absolute.
+template <typename char_type>
+static bool IsRootOrAbsolute(const std::basic_string<char_type>& path,
+ bool must_be_root) {
+ // An absolute path is one that starts with "/", "\", "c:/", "c:\",
+ // "\\?\c:\", or rarely "\??\c:\" or "\\.\c:\".
+ //
+ // It is unclear whether the UNC prefix is just "\\?\" or is "\??\" also
+ // valid (in some cases it seems to be, though MSDN doesn't mention it).
+ return
+ // path is (or starts with) "/" or "\"
+ ((must_be_root ? path.size() == 1 : !path.empty()) &&
+ IsPathSeparator(path[0])) ||
+ // path is (or starts with) "c:/" or "c:\" or similar
+ ((must_be_root ? path.size() == 3 : path.size() >= 3) &&
+ HasDriveSpecifierPrefix(path.c_str()) && IsPathSeparator(path[2])) ||
+ // path is (or starts with) "\\?\c:\" or "\??\c:\" or similar
+ ((must_be_root ? path.size() == 7 : path.size() >= 7) &&
+ HasUncPrefix(path.c_str()) &&
+ HasDriveSpecifierPrefix(path.c_str() + 4) && IsPathSeparator(path[6]));
+}
+
+template <typename char_type>
+static std::pair<std::basic_string<char_type>, std::basic_string<char_type> >
+SplitPathImpl(const std::basic_string<char_type>& path) {
+ if (path.empty()) {
+ return std::make_pair(std::basic_string<char_type>(),
+ std::basic_string<char_type>());
+ }
+
+ size_t pos = path.size() - 1;
+ for (auto it = path.crbegin(); it != path.crend(); ++it, --pos) {
+ if (IsPathSeparator(*it)) {
+ if ((pos == 2 || pos == 6) &&
+ IsRootOrAbsolute(path.substr(0, pos + 1), /* must_be_root */ true)) {
+ // Windows path, top-level directory, e.g. "c:\foo",
+ // result is ("c:\", "foo").
+ // Or UNC path, top-level directory, e.g. "\\?\c:\foo"
+ // result is ("\\?\c:\", "foo").
+ return std::make_pair(
+ // Include the "/" or "\" in the drive specifier.
+ path.substr(0, pos + 1), path.substr(pos + 1));
+ } else {
+ // Windows path (neither top-level nor drive root), Unix path, or
+ // relative path.
+ return std::make_pair(
+ // If the only "/" is the leading one, then that shall be the first
+ // pair element, otherwise the substring up to the rightmost "/".
+ pos == 0 ? path.substr(0, 1) : path.substr(0, pos),
+ // If the rightmost "/" is the tail, then the second pair element
+ // should be empty.
+ pos == path.size() - 1 ? std::basic_string<char_type>()
+ : path.substr(pos + 1));
+ }
+ }
+ }
+ // Handle the case with no '/' or '\' in `path`.
+ return std::make_pair(std::basic_string<char_type>(), path);
+}
+
+std::pair<std::string, std::string> SplitPath(const std::string& path) {
+ return SplitPathImpl(path);
+}
+
+std::pair<std::wstring, std::wstring> SplitPathW(const std::wstring& path) {
+ return SplitPathImpl(path);
+}
+
+bool AsWindowsPath(const std::string& path, std::string* result,
+ std::string* error) {
+ if (path.empty()) {
+ result->clear();
+ return true;
+ }
+ if (IsDevNull(path.c_str())) {
+ result->assign("NUL");
+ return true;
+ }
+ if (HasUncPrefix(path.c_str())) {
+ // Path has "\\?\" prefix --> assume it's already Windows-style.
+ *result = path.c_str();
+ return true;
+ }
+ if (IsPathSeparator(path[0]) && path.size() > 1 && IsPathSeparator(path[1])) {
+ // Unsupported path: "\\" or "\\server\path", or some degenerate form of
+ // these, such as "//foo".
+ if (error) {
+ *error = "network paths are unsupported";
+ }
+ return false;
+ }
+ if (HasDriveSpecifierPrefix(path.c_str()) &&
+ (path.size() < 3 || !IsPathSeparator(path[2]))) {
+ // Unsupported path: "c:" or "c:foo"
+ if (error) {
+ *error = "working-directory relative paths are unsupported";
+ }
+ return false;
+ }
+
+ std::string mutable_path = path;
+ if (path[0] == '/') {
+ if (error) {
+ *error = "Unix-style paths are unsupported";
+ }
+ return false;
+ }
+
+ if (path[0] == '\\') {
+ // This is an absolute Windows path on the current drive, e.g. "\foo\bar".
+ mutable_path = std::string(1, GetCurrentDrive()) + ":" + path;
+ } // otherwise this is a relative path, or absolute Windows path.
+
+ result->assign(NormalizeWindowsPath(mutable_path));
+ return true;
+}
+
+bool AsWindowsPath(const std::string& path, std::wstring* result,
+ std::string* error) {
+ std::string normalized_win_path;
+ if (!AsWindowsPath(path, &normalized_win_path, error)) {
+ return false;
+ }
+
+ result->assign(CstringToWstring(normalized_win_path.c_str()).get());
+ return true;
+}
+
+bool AsAbsoluteWindowsPath(const std::string& path, std::wstring* result,
+ std::string* error) {
+ if (path.empty()) {
+ result->clear();
+ return true;
+ }
+ if (IsDevNull(path.c_str())) {
+ result->assign(L"NUL");
+ return true;
+ }
+ if (!AsWindowsPath(path, result, error)) {
+ return false;
+ }
+ if (!IsRootOrAbsolute(*result, /* must_be_root */ false)) {
+ *result = GetCwdW() + L"\\" + *result;
+ }
+ if (!HasUncPrefix(result->c_str())) {
+ *result = std::wstring(L"\\\\?\\") + *result;
+ }
+ return true;
+}
+
+bool AsShortWindowsPath(const std::string& path, std::string* result,
+ std::string* error) {
+ if (IsDevNull(path.c_str())) {
+ result->assign("NUL");
+ return true;
+ }
+
+ result->clear();
+ std::wstring wpath;
+ std::wstring wsuffix;
+ if (!AsAbsoluteWindowsPath(path, &wpath, error)) {
+ return false;
+ }
+ DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
+ if (size == 0) {
+ // GetShortPathNameW can fail if `wpath` does not exist. This is expected
+ // when we are about to create a file at that path, so instead of failing,
+ // walk up in the path until we find a prefix that exists and can be
+ // shortened, or is a root directory. Save the non-existent tail in
+ // `wsuffix`, we'll add it back later.
+ std::vector<std::wstring> segments;
+ while (size == 0 && !IsRootDirectoryW(wpath)) {
+ std::pair<std::wstring, std::wstring> split = SplitPathW(wpath);
+ wpath = split.first;
+ segments.push_back(split.second);
+ size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
+ }
+
+ // Join all segments.
+ std::wostringstream builder;
+ bool first = true;
+ for (auto it = segments.crbegin(); it != segments.crend(); ++it) {
+ if (!first || !IsRootDirectoryW(wpath)) {
+ builder << L'\\' << *it;
+ } else {
+ builder << *it;
+ }
+ first = false;
+ }
+ wsuffix = builder.str();
+ }
+
+ std::wstring wresult;
+ if (IsRootDirectoryW(wpath)) {
+ // Strip the UNC prefix from `wpath`, and the leading "\" from `wsuffix`.
+ wresult = std::wstring(RemoveUncPrefixMaybe(wpath.c_str())) + wsuffix;
+ } else {
+ std::unique_ptr<WCHAR[]> wshort(
+ new WCHAR[size]); // size includes null-terminator
+ if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
+ if (error) {
+ std::string last_error = GetLastErrorString();
+ std::stringstream msg;
+ msg << "AsShortWindowsPath(" << path << "): GetShortPathNameW("
+ << WstringToString(wpath) << ") failed: " << last_error;
+ *error = msg.str();
+ }
+ return false;
+ }
+ // GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
+ wresult = std::wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix;
+ }
+
+ result->assign(WstringToCstring(wresult.c_str()).get());
+ ToLower(result);
+ return true;
+}
+
+bool IsDevNull(const char* path) {
+ return path != NULL && *path != 0 &&
+ (strncmp("/dev/null\0", path, 10) == 0 ||
+ ((path[0] == 'N' || path[0] == 'n') &&
+ (path[1] == 'U' || path[1] == 'u') &&
+ (path[2] == 'L' || path[2] == 'l') && path[3] == 0));
+}
+
+bool IsRootDirectory(const std::string& path) {
+ return IsRootOrAbsolute(path, true);
+}
+
+bool IsAbsolute(const std::string& path) {
+ return IsRootOrAbsolute(path, false);
+}
+
+bool IsRootDirectoryW(const std::wstring& path) {
+ return IsRootOrAbsolute(path, true);
+}
+
+static char GetCurrentDrive() {
+ std::wstring cwd = GetCwdW();
+ wchar_t wdrive = RemoveUncPrefixMaybe(cwd.c_str())[0];
+ wchar_t offset = wdrive >= L'A' && wdrive <= L'Z' ? L'A' : L'a';
+ return 'a' + wdrive - offset;
+}
+
+std::string NormalizeWindowsPath(std::string path) {
+ if (path.empty()) {
+ return "";
+ }
+ if (path[0] == '/') {
+ // This is an absolute MSYS path, error out.
+ BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+ << "NormalizeWindowsPath(" << path << "): expected a Windows path";
+ }
+ if (path.size() >= 4 && HasUncPrefix(path.c_str())) {
+ path = path.substr(4);
+ }
+
+ static const std::string dot(".");
+ static const std::string dotdot("..");
+
+ std::vector<std::string> segments;
+ int segment_start = -1;
+ // Find the path segments in `path` (separated by "/").
+ for (int i = 0;; ++i) {
+ if (!IsPathSeparator(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 "..".
+ std::string segment(path, segment_start, i - segment_start);
+ segment_start = -1;
+ if (segment == dotdot) {
+ if (!segments.empty() &&
+ !HasDriveSpecifierPrefix(segments[0].c_str())) {
+ segments.pop_back();
+ }
+ } else if (segment != dot) {
+ 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 &&
+ HasDriveSpecifierPrefix(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;
+ }
+ return result.str();
+}
+
+} // namespace blaze_util