| /* | |
| * Copyright (C) 2019 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 "path.h" | |
| #include <android-base/strings.h> | |
| #include <android-base/logging.h> | |
| #include <algorithm> | |
| #include <iterator> | |
| #include <limits> | |
| #include <memory> | |
| #include <dirent.h> | |
| #include <stdlib.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| using namespace std::literals; | |
| namespace android::incremental::path { | |
| bool PathCharsLess::operator()(char l, char r) const { | |
| int ll = l == '/' ? std::numeric_limits<char>::min() - 1 : l; | |
| int rr = r == '/' ? std::numeric_limits<char>::min() - 1 : r; | |
| return ll < rr; | |
| } | |
| bool PathLess::operator()(std::string_view l, std::string_view r) const { | |
| return std::lexicographical_compare(std::begin(l), std::end(l), std::begin(r), std::end(r), | |
| PathCharsLess()); | |
| } | |
| static void preparePathComponent(std::string_view& path, bool trimAll) { | |
| // need to check for double front slash as a single one has a separate meaning in front | |
| while (!path.empty() && path.front() == '/' && | |
| (trimAll || (path.size() > 1 && path[1] == '/'))) { | |
| path.remove_prefix(1); | |
| } | |
| // for the back we don't care about double-vs-single slash difference | |
| while (path.size() > !trimAll && path.back() == '/') { | |
| path.remove_suffix(1); | |
| } | |
| } | |
| void details::append_next_path(std::string& target, std::string_view path) { | |
| preparePathComponent(path, !target.empty()); | |
| if (path.empty()) { | |
| return; | |
| } | |
| if (!target.empty() && !target.ends_with('/')) { | |
| target.push_back('/'); | |
| } | |
| target += path; | |
| } | |
| std::string_view relativize(std::string_view parent, std::string_view nested) { | |
| if (!nested.starts_with(parent)) { | |
| return nested; | |
| } | |
| if (nested.size() == parent.size()) { | |
| return {}; | |
| } | |
| if (nested[parent.size()] != '/') { | |
| return nested; | |
| } | |
| auto relative = nested.substr(parent.size()); | |
| while (relative.front() == '/') { | |
| relative.remove_prefix(1); | |
| } | |
| return relative; | |
| } | |
| bool isAbsolute(std::string_view path) { | |
| return !path.empty() && path[0] == '/'; | |
| } | |
| std::string normalize(std::string_view path) { | |
| if (path.empty()) { | |
| return {}; | |
| } | |
| if (path.starts_with("../"sv)) { | |
| return {}; | |
| } | |
| std::string result; | |
| if (isAbsolute(path)) { | |
| path.remove_prefix(1); | |
| } else { | |
| char buffer[PATH_MAX]; | |
| if (!::getcwd(buffer, sizeof(buffer))) { | |
| return {}; | |
| } | |
| result += buffer; | |
| } | |
| size_t start = 0; | |
| size_t end = 0; | |
| for (; end != path.npos; start = end + 1) { | |
| end = path.find('/', start); | |
| // Next component, excluding the separator | |
| auto part = path.substr(start, end - start); | |
| if (part.empty() || part == "."sv) { | |
| continue; | |
| } | |
| if (part == ".."sv) { | |
| if (result.empty()) { | |
| return {}; | |
| } | |
| auto lastPos = result.rfind('/'); | |
| if (lastPos == result.npos) { | |
| result.clear(); | |
| } else { | |
| result.resize(lastPos); | |
| } | |
| continue; | |
| } | |
| result += '/'; | |
| result += part; | |
| } | |
| return result; | |
| } | |
| std::string_view basename(std::string_view path) { | |
| if (path.empty()) { | |
| return {}; | |
| } | |
| if (path == "/"sv) { | |
| return "/"sv; | |
| } | |
| auto pos = path.rfind('/'); | |
| while (!path.empty() && pos == path.size() - 1) { | |
| path.remove_suffix(1); | |
| pos = path.rfind('/'); | |
| } | |
| if (pos == path.npos) { | |
| return path.empty() ? "/"sv : path; | |
| } | |
| return path.substr(pos + 1); | |
| } | |
| std::string_view dirname(std::string_view path) { | |
| if (path.empty()) { | |
| return {}; | |
| } | |
| if (path == "/"sv) { | |
| return "/"sv; | |
| } | |
| const auto pos = path.rfind('/'); | |
| if (pos == 0) { | |
| return "/"sv; | |
| } | |
| if (pos == path.npos) { | |
| return "."sv; | |
| } | |
| return path.substr(0, pos); | |
| } | |
| details::CStrWrapper::CStrWrapper(std::string_view sv) { | |
| if (!sv.data()) { | |
| mCstr = ""; | |
| } else if (sv[sv.size()] == '\0') { | |
| mCstr = sv.data(); | |
| } else { | |
| mCopy.emplace(sv); | |
| mCstr = mCopy->c_str(); | |
| } | |
| } | |
| std::optional<bool> isEmptyDir(std::string_view dir) { | |
| const auto d = std::unique_ptr<DIR, decltype(&::closedir)>{::opendir(c_str(dir)), ::closedir}; | |
| if (!d) { | |
| if (errno == EPERM || errno == EACCES) { | |
| return std::nullopt; | |
| } | |
| return false; | |
| } | |
| while (auto entry = ::readdir(d.get())) { | |
| if (entry->d_type != DT_DIR) { | |
| return false; | |
| } | |
| if (entry->d_name != "."sv && entry->d_name != ".."sv) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool startsWith(std::string_view path, std::string_view prefix) { | |
| if (!base::StartsWith(path, prefix)) { | |
| return false; | |
| } | |
| return path.size() == prefix.size() || path[prefix.size()] == '/'; | |
| } | |
| } // namespace android::incremental::path |