Commits on Source (2)
......@@ -227,6 +227,14 @@ gkfs_define_option(
DEFAULT_VALUE OFF
)
# use old resolve function
gkfs_define_option(
GKFS_USE_OLD_PATH_RESOLVE
HELP_TEXT "Use the old implementation of the resolve function"
DEFAULT_VALUE OFF
)
cmake_dependent_option(GKFS_INSTALL_TESTS "Install GekkoFS self tests" OFF "GKFS_BUILD_TESTS" OFF)
......@@ -258,6 +266,14 @@ gkfs_define_option(
DESCRIPTION "Compile with support for rename ops (experimental)"
)
## external link support
gkfs_define_option(
GKFS_FOLLOW_EXTERNAL_SYMLINKS
HELP_TEXT "Enable support for following external links for resolving the path"
DEFAULT_VALUE OFF
DESCRIPTION "Compile with lstat usage in path resolve"
)
################################################################################
# Options and variables that control how GekkoFS behaves internally
......
......@@ -259,6 +259,14 @@ if (GKFS_SYMLINK_SUPPORT)
add_definitions(-DHAS_SYMLINKS)
endif ()
if (GKFS_USE_OLD_PATH_RESOLVE)
add_definitions(-DGKFS_USE_OLD_PATH_RESOLVE)
endif ()
if (GKFS_FOLLOW_EXTERNAL_SYMLINKS)
add_definitions(-DGKFS_FOLLOW_EXTERNAL_SYMLINKS)
endif ()
if (GKFS_RENAME_SUPPORT)
# Rename depends on symlink support
add_definitions(-DHAS_SYMLINKS)
......
......@@ -36,7 +36,14 @@ unsigned int
match_components(const std::string& path, unsigned int& path_components,
const std::vector<std::string>& components);
bool
/// @resolve_last_link is used only for the old implementation: GKFS_USE_OLD_PATH_RESOLVE
std::pair<bool, std::string>
resolve(const std::string& path, bool resolve_last_link = true);
std::pair<bool, std::string>
resolve_new(const std::string& path);
[[deprecated("Use GKFS_USE_OLD_PATH_RESOLVE to use old implementation")]] bool
resolve(const std::string& path, std::string& resolved,
bool resolve_last_link = true);
......
......@@ -34,10 +34,11 @@
#include <common/path_util.hpp>
#include <stack>
#include <utility>
#include <vector>
#include <string>
#include <cassert>
#include <climits>
extern "C" {
#include <sys/stat.h>
......@@ -95,6 +96,90 @@ 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(const string& path, bool resolve_last_link) {
#ifdef GKFS_USE_OLD_PATH_RESOLVE
string resolved;
bool is_in_path = resolve(path, resolved, resolve_last_link);
return make_pair(is_in_path, resolved);
#else
return resolve_new(path);
#endif
}
pair<bool, string>
resolve_new(const string& path) {
const string& mountdir = CTX->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
LOG(DEBUG, "path: \"{}\", mountdir: \"{}\"", path,
mountdir);
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_EXTERNAL_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`.
......@@ -324,4 +409,4 @@ set_cwd(const string& path, bool internal) {
CTX->cwd(path);
}
} // namespace gkfs::path
\ No newline at end of file
} // namespace gkfs::path
......@@ -41,6 +41,7 @@
#include <hermes.hpp>
#include <cassert>
#include <utility>
extern "C" {
#include <libsyscall_intercept_hook_point.h>
......@@ -234,7 +235,9 @@ PreloadContext::relativize_fd_path(int dirfd, const char* raw_path,
path = raw_path;
}
if(gkfs::path::resolve(path, relative_path, resolve_last_link)) {
std::pair<bool, std::string> resolved_path = gkfs::path::resolve(path, resolve_last_link);
relative_path = resolved_path.second;
if(resolved_path.first) {
return RelativizeStatus::internal;
}
return RelativizeStatus::external;
......@@ -264,7 +267,10 @@ PreloadContext::relativize_path(const char* raw_path,
path = raw_path;
}
return gkfs::path::resolve(path, relative_path, resolve_last_link);
std::pair<bool, std::string> resolved_path = gkfs::path::resolve(path, resolve_last_link);
relative_path = resolved_path.second;
return resolved_path.first;
}
const std::shared_ptr<gkfs::filemap::OpenFileMap>&
......
......@@ -49,7 +49,7 @@ endif ()
add_subdirectory(helpers)
# create a convenience library with Catch2's main
# create a convenience library with Catch2's main
# to speed up test compilation
add_library(catch2_main STATIC)
target_sources(catch2_main PRIVATE catch_main.cpp)
......@@ -63,6 +63,7 @@ add_executable(tests)
target_sources(tests
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/test_utils_arithmetic.cpp
${CMAKE_CURRENT_LIST_DIR}/test_path.cpp
${CMAKE_CURRENT_LIST_DIR}/test_helpers.cpp)
if(GKFS_TESTS_GUIDED_DISTRIBUTION)
......@@ -76,6 +77,7 @@ target_link_libraries(tests
helpers
arithmetic
distributor
gkfs_intercept
)
# Catch2's contrib folder includes some helper functions
......
/*
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 <client/preload_context.hpp>
// mock CTX
class text_context {
std::string mntdir = "/tmp/gkfs_mount";
const std::string& mountdir(){
return mntdir;
}
};
#define CTX text_context
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("").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/./").second == "/bar/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/tmp/../tmp/gkfs_mount/bar/./").second == "/bar/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/tmp/./gkfs_mount/bar/./").second == "/bar/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/tmp/gkfs_mount/bar/./").second == "/bar/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/home/foo/../.././tmp/gkfs_mount/").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/../ar/.").second == "/home/bar");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/home/foo/../bar/./").second == "/home/bar/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/home/foo/../bar/../").second == "/home/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/home/foo/../../").second == "/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/home/foo/./bar/../").second == "/home/foo/");
}
THEN(" iwas ") {
REQUIRE(gkfs::path::resolve_new("/home/./../tmp/").second == "/tmp/");
}
}
}
}