Loading include/client/path.hpp +3 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,9 @@ unsigned int match_components(const std::string& path, unsigned int& path_components, const std::vector<std::string>& components); std::pair<bool, std::string> resolve_new(const std::string& path, std::string mountdir); bool resolve(const std::string& path, std::string& resolved, bool resolve_last_link = true); Loading src/client/path.cpp +74 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,8 @@ #include <common/path_util.hpp> #include <stack> #include <utility> #include <vector> #include <string> #include <cassert> Loading Loading @@ -100,6 +102,77 @@ match_components(const string& path, unsigned int& path_components, return matched; } string follow_symlinks(const string& path) { struct stat st {}; if(lstat(path.c_str(), &st) < 0) { LOG(DEBUG, "path \"{}\" does not exist", path); return path; } if(S_ISLNK(st.st_mode)) { auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]); if(realpath(path.c_str(), link_resolved.get()) == nullptr) { LOG(ERROR, "Failed to get realpath for link \"{}\". " "Error: {}", path, ::strerror(errno)); return path; } // substituute resolved with new link path return link_resolved.get(); } return path; } pair<bool, string> resolve_new(const string& path, string mountdir) { LOG(DEBUG, "path: \"{}\", mountdir: \"{}\"", path, mountdir); string resolved = ""; stack<size_t> last_component_pos; for(size_t start = 0; start < path.size(); start++) { size_t end = path.find(path::separator, start); size_t comp_size = end - start; if(comp_size == 1 && path.at(start) == path::separator) { // should I use same while loop as in the original here? continue; } if(comp_size == 1 && path.at(start) == '.') { // component is '.', we skip it continue; } if(comp_size == 2 && path.at(start) == '.' && path.at(start + 1) == '.') { // component is '..', we skip it assert(!last_component_pos.empty()); resolved.erase(last_component_pos.top()); last_component_pos.pop(); continue; } // add `/<component>` to the reresolved path resolved.push_back(path::separator); last_component_pos.push(resolved.size() - 1); resolved.append(path, start, comp_size); #ifdef GKFS_FOLLOW_SYMLINKS // HAS_SYMLINKS ??? resolved = follow_symlinks(resolved); #endif } if(resolved.substr(0, mountdir.size()) == mountdir) { resolved.erase(1, CTX->mountdir().size()); LOG(DEBUG, "internal: \"{}\"", resolved); return make_pair(true, resolved); } if(resolved.empty()) { resolved.push_back(path::separator); } LOG(DEBUG, "external: \"{}\"", resolved); return make_pair(false, resolved); } /** Resolve path to its canonical representation * * Populate `resolved` with the canonical representation of `path`. Loading tests/unit/test_path.cpp 0 → 100644 +102 −0 Original line number Diff line number Diff line /* Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain Copyright 2015-2024, Johannes Gutenberg Universitaet Mainz, Germany This software was partially supported by the EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). This software was partially supported by the ADA-FS project under the SPPEXA project funded by the DFG. This file is part of GekkoFS. GekkoFS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GekkoFS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GekkoFS. If not, see <https://www.gnu.org/licenses/>. SPDX-License-Identifier: GPL-3.0-or-later */ #include <catch2/catch.hpp> #include <client/path.hpp> #include <utility> SCENARIO(" resolve fn should handle empty path ", "[test_path][empty]") { GIVEN(" a mount path ") { std::string mntpath = "/tmp/gkfs_mount"; WHEN(" resolve with empty path ") { THEN(" / should be returned ") { REQUIRE(gkfs::path::resolve_new("", mntpath).second == "/"); } } } } // TODO check pair.first is true SCENARIO(" resolve fn should handle internal paths ", "[test_path][external paths]") { GIVEN(" a mount path ") { std::string mntpath = "/tmp/gkfs_mount"; WHEN(" resolve with relative path ") { THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../../tmp/./gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/tmp/../tmp/gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/tmp/./gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/tmp/gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../.././tmp/gkfs_mount/", mntpath).second == "/"); } } } } // TODO check pair.first is false SCENARIO(" resolve fn should handle external paths ", "[test_path][external paths]") { GIVEN(" a mount path ") { std::string mntpath = "/tmp/gkfs_mount"; WHEN(" resolve with relative path ") { THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../bar/./", mntpath).second == "/home/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../bar/../", mntpath).second == "/home/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../../", mntpath).second == "/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/./bar/../", mntpath).second == "/home/foo/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/./../tmp/", mntpath).second == "/tmp/"); } } } } Loading
include/client/path.hpp +3 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,9 @@ unsigned int match_components(const std::string& path, unsigned int& path_components, const std::vector<std::string>& components); std::pair<bool, std::string> resolve_new(const std::string& path, std::string mountdir); bool resolve(const std::string& path, std::string& resolved, bool resolve_last_link = true); Loading
src/client/path.cpp +74 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,8 @@ #include <common/path_util.hpp> #include <stack> #include <utility> #include <vector> #include <string> #include <cassert> Loading Loading @@ -100,6 +102,77 @@ match_components(const string& path, unsigned int& path_components, return matched; } string follow_symlinks(const string& path) { struct stat st {}; if(lstat(path.c_str(), &st) < 0) { LOG(DEBUG, "path \"{}\" does not exist", path); return path; } if(S_ISLNK(st.st_mode)) { auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]); if(realpath(path.c_str(), link_resolved.get()) == nullptr) { LOG(ERROR, "Failed to get realpath for link \"{}\". " "Error: {}", path, ::strerror(errno)); return path; } // substituute resolved with new link path return link_resolved.get(); } return path; } pair<bool, string> resolve_new(const string& path, string mountdir) { LOG(DEBUG, "path: \"{}\", mountdir: \"{}\"", path, mountdir); string resolved = ""; stack<size_t> last_component_pos; for(size_t start = 0; start < path.size(); start++) { size_t end = path.find(path::separator, start); size_t comp_size = end - start; if(comp_size == 1 && path.at(start) == path::separator) { // should I use same while loop as in the original here? continue; } if(comp_size == 1 && path.at(start) == '.') { // component is '.', we skip it continue; } if(comp_size == 2 && path.at(start) == '.' && path.at(start + 1) == '.') { // component is '..', we skip it assert(!last_component_pos.empty()); resolved.erase(last_component_pos.top()); last_component_pos.pop(); continue; } // add `/<component>` to the reresolved path resolved.push_back(path::separator); last_component_pos.push(resolved.size() - 1); resolved.append(path, start, comp_size); #ifdef GKFS_FOLLOW_SYMLINKS // HAS_SYMLINKS ??? resolved = follow_symlinks(resolved); #endif } if(resolved.substr(0, mountdir.size()) == mountdir) { resolved.erase(1, CTX->mountdir().size()); LOG(DEBUG, "internal: \"{}\"", resolved); return make_pair(true, resolved); } if(resolved.empty()) { resolved.push_back(path::separator); } LOG(DEBUG, "external: \"{}\"", resolved); return make_pair(false, resolved); } /** Resolve path to its canonical representation * * Populate `resolved` with the canonical representation of `path`. Loading
tests/unit/test_path.cpp 0 → 100644 +102 −0 Original line number Diff line number Diff line /* Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain Copyright 2015-2024, Johannes Gutenberg Universitaet Mainz, Germany This software was partially supported by the EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). This software was partially supported by the ADA-FS project under the SPPEXA project funded by the DFG. This file is part of GekkoFS. GekkoFS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GekkoFS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GekkoFS. If not, see <https://www.gnu.org/licenses/>. SPDX-License-Identifier: GPL-3.0-or-later */ #include <catch2/catch.hpp> #include <client/path.hpp> #include <utility> SCENARIO(" resolve fn should handle empty path ", "[test_path][empty]") { GIVEN(" a mount path ") { std::string mntpath = "/tmp/gkfs_mount"; WHEN(" resolve with empty path ") { THEN(" / should be returned ") { REQUIRE(gkfs::path::resolve_new("", mntpath).second == "/"); } } } } // TODO check pair.first is true SCENARIO(" resolve fn should handle internal paths ", "[test_path][external paths]") { GIVEN(" a mount path ") { std::string mntpath = "/tmp/gkfs_mount"; WHEN(" resolve with relative path ") { THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../../tmp/./gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/tmp/../tmp/gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/tmp/./gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/tmp/gkfs_mount/bar/./", mntpath).second == "/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../.././tmp/gkfs_mount/", mntpath).second == "/"); } } } } // TODO check pair.first is false SCENARIO(" resolve fn should handle external paths ", "[test_path][external paths]") { GIVEN(" a mount path ") { std::string mntpath = "/tmp/gkfs_mount"; WHEN(" resolve with relative path ") { THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../bar/./", mntpath).second == "/home/bar/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../bar/../", mntpath).second == "/home/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/../../", mntpath).second == "/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/foo/./bar/../", mntpath).second == "/home/foo/"); } THEN(" iwas ") { REQUIRE(gkfs::path::resolve_new("/home/./../tmp/", mntpath).second == "/tmp/"); } } } }