| // Copyright 2020 The Android Open Source Project |
| // |
| // 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 "base/PathUtils.h" |
| |
| #include <string.h> // for size_t, strncmp |
| #include <iterator> // for reverse_iterator, operator!= |
| #include <numeric> // for accumulate |
| #include <type_traits> // for enable_if<>::type |
| |
| #ifndef _WIN32 |
| #include <unistd.h> |
| #endif |
| |
| #ifdef _WIN32 |
| #include "Win32UnicodeString.h" |
| #endif |
| |
| static inline bool sIsEmpty(const char* str) { |
| return !str || str[0] == '\0'; |
| } |
| |
| namespace android { |
| namespace base { |
| |
| const char* const PathUtils::kExeNameSuffixes[kHostTypeCount] = {"", ".exe"}; |
| |
| const char* const PathUtils::kExeNameSuffix = |
| PathUtils::kExeNameSuffixes[PathUtils::HOST_TYPE]; |
| |
| std::string PathUtils::toExecutableName(const char* baseName, |
| HostType hostType) { |
| return static_cast<std::string>(baseName).append( |
| kExeNameSuffixes[hostType]); |
| } |
| |
| // static |
| bool PathUtils::isDirSeparator(int ch, HostType hostType) { |
| return (ch == '/') || (hostType == HOST_WIN32 && ch == '\\'); |
| } |
| |
| // static |
| bool PathUtils::isPathSeparator(int ch, HostType hostType) { |
| return (hostType == HOST_POSIX && ch == ':') || |
| (hostType == HOST_WIN32 && ch == ';'); |
| } |
| |
| // static |
| size_t PathUtils::rootPrefixSize(const std::string& path, HostType hostType) { |
| if (path.empty()) return 0; |
| |
| if (hostType != HOST_WIN32) |
| return (path[0] == '/') ? 1U : 0U; |
| |
| size_t result = 0; |
| if (path[1] == ':') { |
| int ch = path[0]; |
| if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) |
| result = 2U; |
| } else if (!strncmp(path.c_str(), "\\\\.\\", 4) || |
| !strncmp(path.c_str(), "\\\\?\\", 4)) { |
| // UNC prefixes. |
| return 4U; |
| } else if (isDirSeparator(path[0], hostType)) { |
| result = 1; |
| if (isDirSeparator(path[1], hostType)) { |
| result = 2; |
| while (path[result] && !isDirSeparator(path[result], HOST_WIN32)) |
| result++; |
| } |
| } |
| if (result && path[result] && isDirSeparator(path[result], HOST_WIN32)) |
| result++; |
| |
| return result; |
| } |
| |
| // static |
| bool PathUtils::isAbsolute(const char* path, HostType hostType) { |
| size_t prefixSize = rootPrefixSize(path, hostType); |
| if (!prefixSize) { |
| return false; |
| } |
| if (hostType != HOST_WIN32) { |
| return true; |
| } |
| return isDirSeparator(path[prefixSize - 1], HOST_WIN32); |
| } |
| |
| // static |
| std::string PathUtils::removeTrailingDirSeparator(const char* path, |
| HostType hostType) { |
| size_t pathLen = strlen(path); |
| // NOTE: Don't remove initial path separator for absolute paths. |
| while (pathLen > 1U && isDirSeparator(path[pathLen - 1U], hostType)) { |
| pathLen--; |
| } |
| return std::string(path, pathLen); |
| } |
| |
| // static |
| std::string PathUtils::addTrailingDirSeparator(const char* path, |
| HostType hostType) { |
| std::string result = path; |
| if (result.size() > 0 && !isDirSeparator(result[result.size() - 1U])) { |
| result += getDirSeparator(hostType); |
| } |
| return result; |
| } |
| |
| // static |
| bool PathUtils::split(const char* path, |
| HostType hostType, |
| const char** dirName, |
| const char** baseName) { |
| if (sIsEmpty(path)) { |
| return false; |
| } |
| |
| // If there is a trailing directory separator, return an error. |
| size_t end = strlen(path); |
| if (isDirSeparator(path[end - 1U], hostType)) { |
| return false; |
| } |
| |
| // Find last separator. |
| size_t prefixLen = rootPrefixSize(path, hostType); |
| size_t pos = end; |
| while (pos > prefixLen && !isDirSeparator(path[pos - 1U], hostType)) { |
| pos--; |
| } |
| |
| // Handle common case. |
| if (pos > prefixLen) { |
| if (dirName) { |
| *dirName = path; |
| } |
| if (baseName) { |
| *baseName = path + pos; |
| } |
| return true; |
| } |
| |
| // If there is no directory separator, the path is a single file name. |
| if (dirName) { |
| if (!prefixLen) { |
| *dirName = "."; |
| } else { |
| *dirName = path; |
| } |
| } |
| if (baseName) { |
| *baseName = path + prefixLen; |
| } |
| return true; |
| } |
| |
| // static |
| std::string PathUtils::join(const std::string& path1, |
| const std::string& path2, |
| HostType hostType) { |
| if (path1.empty()) { |
| return path2; |
| } |
| if (path2.empty()) { |
| return path1; |
| } |
| if (isAbsolute(path2.c_str(), hostType)) { |
| return path2; |
| } |
| size_t prefixLen = rootPrefixSize(path1, hostType); |
| std::string result(path1); |
| size_t end = result.size(); |
| if (end > prefixLen && !isDirSeparator(result[end - 1U], hostType)) { |
| result += getDirSeparator(hostType); |
| } |
| result += path2; |
| return result; |
| } |
| |
| // static |
| template <class String> |
| std::vector<String> PathUtils::decompose(const String& path, |
| HostType hostType) { |
| std::vector<String> result; |
| if (path.empty()) |
| return result; |
| |
| size_t prefixLen = rootPrefixSize(path, hostType); |
| auto it = path.begin(); |
| if (prefixLen) { |
| result.emplace_back(it, it + prefixLen); |
| it += prefixLen; |
| } |
| for (;;) { |
| auto p = it; |
| while (*p && !isDirSeparator(*p, hostType)) |
| p++; |
| if (p > it) { |
| result.emplace_back(it, p); |
| } |
| if (!*p) { |
| break; |
| } |
| it = p + 1; |
| } |
| return result; |
| } |
| |
| std::vector<std::string> PathUtils::decompose(std::string&& path, |
| HostType hostType) { |
| return decompose<std::string>(path, hostType); |
| } |
| |
| std::vector<std::string> PathUtils::decompose(const std::string& path, |
| HostType hostType) { |
| return decompose<std::string>(path, hostType); |
| } |
| |
| template <class String> |
| std::string PathUtils::recompose(const std::vector<String>& components, |
| HostType hostType) { |
| if (components.empty()) { |
| return {}; |
| } |
| |
| const char dirSeparator = getDirSeparator(hostType); |
| std::string result; |
| // To reduce memory allocations, compute capacity before doing the |
| // real append. |
| const size_t capacity = |
| components.size() - 1 + |
| std::accumulate(components.begin(), components.end(), size_t(0), |
| [](size_t val, const String& next) { |
| return val + next.size(); |
| }); |
| |
| result.reserve(capacity); |
| bool addSeparator = false; |
| for (size_t n = 0; n < components.size(); ++n) { |
| const auto& component = components[n]; |
| if (addSeparator) |
| result += dirSeparator; |
| addSeparator = true; |
| if (n == 0) { |
| size_t prefixLen = rootPrefixSize(component, hostType); |
| if (prefixLen == component.size()) { |
| addSeparator = false; |
| } |
| } |
| result += component; |
| } |
| return result; |
| } |
| |
| // static |
| std::string PathUtils::recompose(const std::vector<std::string>& components, |
| HostType hostType) { |
| return recompose<std::string>(components, hostType); |
| } |
| |
| // static |
| template <class String> |
| void PathUtils::simplifyComponents(std::vector<String>* components) { |
| std::vector<String> stack; |
| for (auto& component : *components) { |
| if (component == ".") { |
| // Ignore any instance of '.' from the list. |
| continue; |
| } |
| if (component == "..") { |
| // Handling of '..' is specific: if there is a item on the |
| // stack that is not '..', then remove it, otherwise push |
| // the '..'. |
| if (!stack.empty() && stack.back() != "..") { |
| stack.pop_back(); |
| } else { |
| stack.push_back(std::move(component)); |
| } |
| continue; |
| } |
| // If not a '..', just push on the stack. |
| stack.push_back(std::move(component)); |
| } |
| if (stack.empty()) { |
| stack.push_back("."); |
| } |
| components->swap(stack); |
| } |
| |
| void PathUtils::simplifyComponents(std::vector<std::string>* components) { |
| simplifyComponents<std::string>(components); |
| } |
| |
| #ifdef _WIN32 |
| |
| // Return |path| as a Unicode string, while discarding trailing separators. |
| Win32UnicodeString win32Path(const char* path) { |
| Win32UnicodeString wpath(path); |
| // Get rid of trailing directory separators, Windows doesn't like them. |
| size_t size = wpath.size(); |
| while (size > 0U && |
| (wpath[size - 1U] == L'\\' || wpath[size - 1U] == L'/')) { |
| size--; |
| } |
| if (size < wpath.size()) { |
| wpath.resize(size); |
| } |
| return wpath; |
| } |
| |
| static int GetWin32Mode(int mode) { |
| // Convert |mode| to win32 permission bits. |
| int win32mode = 0x0; |
| |
| if ((mode & R_OK) || (mode & X_OK)) { |
| win32mode |= 0x4; |
| } |
| if (mode & W_OK) { |
| win32mode |= 0x2; |
| } |
| |
| return win32mode; |
| } |
| |
| #endif |
| |
| bool pathExists(const char* path) { |
| #ifdef _WIN32 |
| return _waccess(win32Path(path).c_str(), GetWin32Mode(F_OK)); |
| #else |
| return 0 == access(path, F_OK); |
| #endif |
| } |
| |
| std::string pj(const std::vector<std::string>& paths) { |
| std::string res; |
| |
| if (paths.size() == 0) |
| return ""; |
| |
| if (paths.size() == 1) |
| return paths[0]; |
| |
| res = paths[0]; |
| |
| for (size_t i = 1; i < paths.size(); i++) { |
| res = PathUtils::join(res, paths[i]); |
| } |
| |
| return res; |
| } |
| |
| std::string PathUtils::addTrailingDirSeparator(const std::string& path, |
| HostType hostType) { |
| std::string result = path; |
| if (result.size() > 0 && !isDirSeparator(result[result.size() - 1U])) { |
| result += getDirSeparator(hostType); |
| } |
| return result; |
| } |
| |
| } // namespace base |
| } // namespace android |