| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you 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 "paimon/common/utils/path_util.h" | |
| #include <unistd.h> | |
| #include <algorithm> | |
| #include <cctype> | |
| #include <cerrno> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <utility> | |
| #include "fmt/format.h" | |
| #include "paimon/common/utils/string_utils.h" | |
| #include "paimon/common/utils/uuid.h" | |
| #include "paimon/status.h" | |
| namespace paimon { | |
| namespace { | |
| /// Escapes the control characters of `name`, so that a rejected name cannot inject a line into | |
| /// the log the error is written to nor truncate the C string it is copied into. Bytes of a | |
| /// multi-byte sequence are left alone, since none of them is a control character. | |
| std::string EscapeControlCharacters(const std::string& name) { | |
| std::string escaped; | |
| escaped.reserve(name.size()); | |
| for (char c : name) { | |
| switch (c) { | |
| case '\\': | |
| escaped += "\\\\"; | |
| break; | |
| case '\n': | |
| escaped += "\\n"; | |
| break; | |
| case '\r': | |
| escaped += "\\r"; | |
| break; | |
| case '\t': | |
| escaped += "\\t"; | |
| break; | |
| default: | |
| if (std::iscntrl(static_cast<unsigned char>(c)) != 0) { | |
| escaped += fmt::format("\\x{{{:02x}}}", static_cast<unsigned char>(c)); | |
| } else { | |
| escaped += c; | |
| } | |
| } | |
| } | |
| return escaped; | |
| } | |
| } // namespace | |
| std::string Path::ToString() const { | |
| std::string ret; | |
| if (!scheme.empty()) { | |
| ret = scheme + ":"; | |
| } | |
| if (!authority.empty()) { | |
| ret += "//"; | |
| ret += authority; | |
| } | |
| if (!path.empty()) { | |
| ret += path; | |
| } | |
| return ret; | |
| } | |
| std::string PathUtil::JoinPath(const std::string& path, const std::string& name) noexcept { | |
| if (path.empty()) { | |
| return name; | |
| } | |
| if (name.empty()) { | |
| return path; | |
| } | |
| int32_t slash_cnt = (*(path.rbegin()) == '/') + (*(name.begin()) == '/'); | |
| if (!slash_cnt) { | |
| return path + "/" + name; | |
| } else if (slash_cnt == 2) { | |
| return path + name.substr(1); | |
| } | |
| return path + name; | |
| } | |
| std::string PathUtil::NormalizeInnerPath(const std::string& path) noexcept { | |
| if (path.empty()) { | |
| return path; | |
| } | |
| std::string ret; | |
| ret.reserve(path.size()); | |
| char last_char = path[0]; | |
| ret.append(1, last_char); | |
| for (size_t i = 1; i < path.size(); ++i) { | |
| if (last_char == '/' && path[i] == '/') { | |
| continue; | |
| } | |
| last_char = path[i]; | |
| ret.append(1, last_char); | |
| } | |
| TrimLastDelim(&ret); | |
| return ret; | |
| } | |
| Result<std::string> PathUtil::NormalizePath(const std::string& path_str) noexcept { | |
| PAIMON_ASSIGN_OR_RAISE(Path path, ToPath(path_str)); | |
| return path.ToString(); | |
| } | |
| Result<Path> PathUtil::ToPath(const std::string& path) noexcept { | |
| // TODO(yonghao.fyh): support Windows Driver | |
| if (path.empty()) { | |
| return Status::Invalid("path is an empty string."); | |
| } | |
| std::string scheme; | |
| std::string authority; | |
| int32_t start = 0; | |
| // parse scheme | |
| auto colon = path.find(':'); | |
| auto slash = path.find('/'); | |
| if ((colon != std::string::npos) && (slash == std::string::npos || colon < slash)) { | |
| // has a scheme | |
| scheme.append(path, 0, colon); | |
| start = colon + 1; | |
| } | |
| // parse authority | |
| if (StringUtils::StartsWith(path, "//", start) && (path.length() - start > 2)) { | |
| // has authority | |
| int32_t next_slash = path.find('/', start + 2); | |
| int32_t auth_end = next_slash > 0 ? next_slash : path.length(); | |
| authority = path.substr(start + 2, auth_end - start - 2); | |
| start = auth_end; | |
| } | |
| // parse path in uri | |
| std::string inner_path = NormalizeInnerPath(path.substr(start)); | |
| return Path(scheme, authority, inner_path); | |
| } | |
| std::string PathUtil::GetParentDirPath(const std::string& path) noexcept { | |
| std::string::const_reverse_iterator it; | |
| for (it = path.rbegin(); it != path.rend() && *it == '/'; it++) { | |
| } | |
| for (; it != path.rend() && *it != '/'; it++) { | |
| } | |
| for (; it != path.rend() && *it == '/'; it++) { | |
| } | |
| return path.substr(0, path.rend() - it); | |
| } | |
| std::string PathUtil::GetName(const std::string& path) noexcept { | |
| std::string dir_path = path; | |
| TrimLastDelim(&dir_path); | |
| std::string::const_reverse_iterator it; | |
| for (it = dir_path.rbegin(); it != dir_path.rend() && *it != '/'; it++) { | |
| } | |
| return dir_path.substr(dir_path.rend() - it); | |
| } | |
| void PathUtil::TrimLastDelim(std::string* dir_path) noexcept { | |
| if (dir_path == nullptr || dir_path->empty()) { | |
| return; | |
| } | |
| if (dir_path->length() > 1 && *(dir_path->rbegin()) == '/') { | |
| dir_path->erase(dir_path->size() - 1, 1); | |
| } | |
| } | |
| Result<std::string> PathUtil::GetWorkingDirectory() noexcept { | |
| char* path = getcwd(nullptr, 0); | |
| if (path != nullptr) { | |
| std::string ret(path); | |
| free(path); | |
| return ret; | |
| } | |
| return Status::IOError( | |
| fmt::format("get working directory failed, ec: {}", std::strerror(errno))); | |
| } | |
| Result<std::string> PathUtil::CreateTempPath(const std::string& path) noexcept { | |
| std::string uuid; | |
| if (!UUID::Generate(&uuid)) { | |
| return Status::Invalid("generate uuid failed"); | |
| } | |
| return JoinPath(GetParentDirPath(path), fmt::format(".{}.{}.tmp", GetName(path), uuid)); | |
| } | |
| Status PathUtil::CheckSinglePathComponent(const std::string& kind, const std::string& name) { | |
| const char* reason = nullptr; | |
| if (StringUtils::IsNullOrWhitespaceOnly(name)) { | |
| reason = "cannot be empty or whitespace"; | |
| } else if (name == "." || name == "..") { | |
| reason = "cannot be '.' or '..'"; | |
| } else if (name.find('/') != std::string::npos || name.find('\\') != std::string::npos) { | |
| reason = "cannot contain path separators"; | |
| } else if (std::any_of(name.begin(), name.end(), [](char c) { | |
| return std::iscntrl(static_cast<unsigned char>(c)) != 0; | |
| })) { | |
| reason = "cannot contain control characters"; | |
| } | |
| if (reason != nullptr) { | |
| return Status::Invalid( | |
| fmt::format("{} name {}: '{}'", kind, reason, EscapeControlCharacters(name))); | |
| } | |
| return Status::OK(); | |
| } | |
| } // namespace paimon |