LCOV - code coverage report
Current view: top level - src/client - path.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 86 196 43.9 %
Date: 2024-04-23 00:09:24 Functions: 9 11 81.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain
       3             :   Copyright 2015-2024, Johannes Gutenberg Universitaet Mainz, Germany
       4             : 
       5             :   This software was partially supported by the
       6             :   EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).
       7             : 
       8             :   This software was partially supported by the
       9             :   ADA-FS project under the SPPEXA project funded by the DFG.
      10             : 
      11             :   This file is part of GekkoFS' POSIX interface.
      12             : 
      13             :   GekkoFS' POSIX interface is free software: you can redistribute it and/or
      14             :   modify it under the terms of the GNU Lesser General Public License as
      15             :   published by the Free Software Foundation, either version 3 of the License,
      16             :   or (at your option) any later version.
      17             : 
      18             :   GekkoFS' POSIX interface is distributed in the hope that it will be useful,
      19             :   but WITHOUT ANY WARRANTY; without even the implied warranty of
      20             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      21             :   GNU Lesser General Public License for more details.
      22             : 
      23             :   You should have received a copy of the GNU Lesser General Public License
      24             :   along with GekkoFS' POSIX interface.  If not, see
      25             :   <https://www.gnu.org/licenses/>.
      26             : 
      27             :   SPDX-License-Identifier: LGPL-3.0-or-later
      28             : */
      29             : 
      30             : #include <client/path.hpp>
      31             : #include <client/preload.hpp>
      32             : #include <client/logging.hpp>
      33             : #include <client/env.hpp>
      34             : 
      35             : #include <common/path_util.hpp>
      36             : 
      37             : #include <stack>
      38             : #include <utility>
      39             : #include <vector>
      40             : #include <string>
      41             : #include <cassert>
      42             : 
      43             : #ifndef BYPASS_SYSCALL
      44             : #include <libsyscall_intercept_hook_point.h>
      45             : #else
      46             : #include <client/void_syscall_intercept.hpp>
      47             : #endif
      48             : 
      49             : extern "C" {
      50             : #include <sys/stat.h>
      51             : }
      52             : 
      53             : using namespace std;
      54             : 
      55             : namespace gkfs::path {
      56             : 
      57             : static const string excluded_paths[2] = {"sys/", "proc/"};
      58             : 
      59             : /** Match components in path
      60             :  *
      61             :  * Returns the number of consecutive components at start of `path`
      62             :  * that match the ones in `components` vector.
      63             :  *
      64             :  * `path_components` will be set to the total number of components found in
      65             :  * `path`
      66             :  *
      67             :  * Example:
      68             :  * ```ÏÏ
      69             :  *  unsigned int tot_comp;
      70             :  *  path_match_components("/matched/head/with/tail", &tot_comp, ["matched",
      71             :  * "head", "no"]) == 2; tot_comp == 4;
      72             :  * ```
      73             :  */
      74             : unsigned int
      75           0 : match_components(const string& path, unsigned int& path_components,
      76             :                  const ::vector<string>& components) {
      77           0 :     unsigned int matched = 0;
      78           0 :     unsigned int processed_components = 0;
      79           0 :     string::size_type comp_size = 0; // size of current component
      80           0 :     string::size_type start = 0;     // start index of curr component
      81           0 :     string::size_type end = 0; // end index of curr component (last processed
      82             :                                // Path Separator "separator")
      83             : 
      84           0 :     while(++end < path.size()) {
      85           0 :         start = end;
      86             : 
      87             :         // Find next component
      88           0 :         end = path.find(path::separator, start);
      89           0 :         if(end == string::npos) {
      90           0 :             end = path.size();
      91             :         }
      92             : 
      93           0 :         comp_size = end - start;
      94           0 :         if(matched == processed_components &&
      95           0 :            path.compare(start, comp_size, components.at(matched)) == 0) {
      96           0 :             ++matched;
      97             :         }
      98           0 :         ++processed_components;
      99             :     }
     100           0 :     path_components = processed_components;
     101           0 :     return matched;
     102             : }
     103             : 
     104             : string
     105      129890 : follow_symlinks(const string& path) {
     106      129890 :     struct stat st {};
     107      129890 :     if(lstat(path.c_str(), &st) < 0) {
     108        2718 :         LOG(DEBUG, "path \"{}\" does not exist", path);
     109        2718 :         return path;
     110             :     }
     111      127172 :     if(S_ISLNK(st.st_mode)) {
     112           6 :         auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]);
     113           3 :         if(realpath(path.c_str(), link_resolved.get()) == nullptr) {
     114             : 
     115           0 :             LOG(ERROR,
     116             :                 "Failed to get realpath for link \"{}\". "
     117             :                 "Error: {}",
     118           0 :                 path, ::strerror(errno));
     119           0 :             return path;
     120             :         }
     121             :         // substituute resolved with new link path
     122           3 :         return link_resolved.get();
     123             :     }
     124      127169 :     return path;
     125             : }
     126             : 
     127             : pair<bool, string>
     128        9915 : resolve(const string& path, bool resolve_last_link) {
     129             : #ifdef GKFS_USE_LEGACY_PATH_RESOLVE
     130             :     string resolved;
     131             :     bool is_in_path = resolve(path, resolved, resolve_last_link);
     132             :     return make_pair(is_in_path, resolved);
     133             : #else
     134        9915 :     return resolve_new(path);
     135             : #endif
     136             : }
     137             : 
     138             : pair<bool, string>
     139        9961 : resolve_new(const string& path) {
     140        9961 :     LOG(DEBUG, "path: \"{}\", mountdir: \"{}\"", path, CTX->mountdir());
     141             : 
     142        9961 :     if(path.empty()) {
     143           2 :         return make_pair(false, "/");
     144             :     }
     145             : 
     146       19920 :     string resolved = "";
     147       19918 :     stack<size_t> last_component_pos;
     148        9959 :     const string absolute_path = (path.at(0) == path::separator)
     149             :                                          ? path
     150       19918 :                                          : CTX->cwd() + path::separator + path;
     151             : 
     152      150364 :     for(size_t start = 0; start < absolute_path.size(); start++) {
     153      140405 :         size_t end = absolute_path.find(path::separator, start);
     154             :         // catches the case without separator at the end
     155      140405 :         if(end == string::npos) {
     156        9929 :             end = absolute_path.size();
     157             :         }
     158      140405 :         size_t comp_size = end - start;
     159      140405 :         if(comp_size == 0 && absolute_path.at(start) == path::separator) {
     160       10162 :             continue;
     161             :         }
     162      130243 :         if(comp_size == 1 && absolute_path.at(start) == '.') {
     163             :             // component is '.', we skip it
     164         200 :             continue;
     165             :         }
     166      130043 :         if(comp_size == 2 && absolute_path.at(start) == '.' &&
     167         153 :            absolute_path.at(start + 1) == '.') {
     168             :             // component is '..', we skip it
     169         153 :             LOG(DEBUG, "path: \"{}\", mountdir: \"{}\"", absolute_path,
     170         153 :                 CTX->mountdir());
     171         153 :             if(last_component_pos.empty()) {
     172          58 :                 resolved = "";
     173             :             } else {
     174          95 :                 resolved.erase(last_component_pos.top());
     175          95 :                 last_component_pos.pop();
     176             :             }
     177         153 :             continue;
     178             :         }
     179             :         // add `/<component>` to the reresolved path
     180      129890 :         resolved.push_back(path::separator);
     181      129890 :         last_component_pos.push(resolved.size() - 1);
     182      129890 :         resolved.append(absolute_path, start, comp_size);
     183      129890 :         start = end;
     184             : 
     185             : #ifdef GKFS_FOLLOW_EXTERNAL_SYMLINKS
     186      129890 :         resolved = follow_symlinks(resolved);
     187             : #endif
     188             :     }
     189             : 
     190        9959 :     if(resolved.substr(0, CTX->mountdir().size()) == CTX->mountdir()) {
     191        1308 :         resolved.erase(1, CTX->mountdir().size());
     192        1308 :         LOG(DEBUG, "internal: \"{}\"", resolved);
     193        1308 :         return make_pair(true, resolved);
     194             :     }
     195             : 
     196        8651 :     if(resolved.empty()) {
     197           6 :         resolved.push_back(path::separator);
     198             :     }
     199        8651 :     LOG(DEBUG, "external: \"{}\"", resolved);
     200       18610 :     return make_pair(false, resolved);
     201             : }
     202             : 
     203             : /** Resolve path to its canonical representation
     204             :  *
     205             :  * Populate `resolved` with the canonical representation of `path`.
     206             :  *
     207             :  * ".", ".." and symbolic links gets resolved.
     208             :  *
     209             :  * If `resolve_last_link` is false, the last components in path
     210             :  * won't be resolved if its a link.
     211             :  *
     212             :  * returns true if the resolved path fall inside GekkoFS namespace,
     213             :  * and false otherwise.
     214             :  */
     215             : bool
     216           0 : resolve(const string& path, string& resolved, bool resolve_last_link) {
     217             : 
     218           0 :     LOG(DEBUG, "path: \"{}\", resolved: \"{}\", resolve_last_link: {}", path,
     219           0 :         resolved, resolve_last_link);
     220             : 
     221           0 :     assert(path::is_absolute(path));
     222             : 
     223           0 :     for(auto& excl_path : excluded_paths) {
     224           0 :         if(path.compare(1, excl_path.length(), excl_path) == 0) {
     225           0 :             LOG(DEBUG, "Skipping: '{}'", path);
     226           0 :             resolved = path;
     227           0 :             return false;
     228             :         }
     229             :     }
     230             : 
     231           0 :     struct stat st {};
     232           0 :     const ::vector<string>& mnt_components = CTX->mountdir_components();
     233           0 :     unsigned int matched_components =
     234             :             0; // matched number of component in mountdir
     235           0 :     unsigned int resolved_components = 0;
     236           0 :     string::size_type comp_size = 0; // size of current component
     237           0 :     string::size_type start = 0;     // start index of curr component
     238           0 :     string::size_type end = 0; // end index of curr component (last processed
     239             :                                // Path Separator "separator")
     240           0 :     string::size_type last_slash_pos =
     241             :             0; // index of last slash in resolved path
     242           0 :     resolved.clear();
     243           0 :     resolved.reserve(path.size());
     244             : 
     245           0 :     while(++end < path.size()) {
     246             :         start = end;
     247             : 
     248             :         /* Skip sequence of multiple path-separators. */
     249           0 :         while(start < path.size() && path[start] == path::separator) {
     250           0 :             ++start;
     251             :         }
     252             : 
     253             :         // Find next component
     254           0 :         end = path.find(path::separator, start);
     255           0 :         if(end == string::npos) {
     256           0 :             end = path.size();
     257             :         }
     258           0 :         comp_size = end - start;
     259             : 
     260           0 :         if(comp_size == 0) {
     261             :             // component is empty (this must be the last component)
     262             :             break;
     263             :         }
     264           0 :         if(comp_size == 1 && path.at(start) == '.') {
     265             :             // component is '.', we skip it
     266           0 :             continue;
     267             :         }
     268           0 :         if(comp_size == 2 && path.at(start) == '.' &&
     269           0 :            path.at(start + 1) == '.') {
     270             :             // component is '..' we need to rollback resolved path
     271           0 :             if(!resolved.empty()) {
     272           0 :                 resolved.erase(last_slash_pos);
     273             :                 /* TODO     Optimization
     274             :                  * the previous slash position should be stored.
     275             :                  * The following search could be avoided.
     276             :                  */
     277           0 :                 last_slash_pos = resolved.find_last_of(path::separator);
     278             :             }
     279           0 :             if(resolved_components > 0) {
     280           0 :                 if(matched_components == resolved_components) {
     281           0 :                     --matched_components;
     282             :                 }
     283           0 :                 --resolved_components;
     284             :             }
     285           0 :             continue;
     286             :         }
     287             : 
     288             :         // add `/<component>` to the reresolved path
     289           0 :         resolved.push_back(path::separator);
     290           0 :         last_slash_pos = resolved.size() - 1;
     291           0 :         resolved.append(path, start, comp_size);
     292             : 
     293           0 :         if(matched_components < mnt_components.size()) {
     294             :             // Outside GekkoFS
     295           0 :             if(matched_components == resolved_components &&
     296           0 :                path.compare(start, comp_size,
     297           0 :                             mnt_components.at(matched_components)) == 0) {
     298           0 :                 ++matched_components;
     299             :             }
     300           0 :             if(lstat(resolved.c_str(), &st) < 0) {
     301             : 
     302           0 :                 LOG(DEBUG, "path \"{}\" does not exist", resolved);
     303             : 
     304           0 :                 resolved.append(path, end, string::npos);
     305           0 :                 return false;
     306             :             }
     307           0 :             if(S_ISLNK(st.st_mode)) {
     308           0 :                 if(!resolve_last_link && end == path.size()) {
     309           0 :                     continue;
     310             :                 }
     311           0 :                 auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]);
     312           0 :                 if(realpath(resolved.c_str(), link_resolved.get()) == nullptr) {
     313             : 
     314           0 :                     LOG(ERROR,
     315             :                         "Failed to get realpath for link \"{}\". "
     316             :                         "Error: {}",
     317           0 :                         resolved, ::strerror(errno));
     318             : 
     319           0 :                     resolved.append(path, end, string::npos);
     320           0 :                     return false;
     321             :                 }
     322             :                 // substituute resolved with new link path
     323           0 :                 resolved = link_resolved.get();
     324           0 :                 matched_components = match_components(
     325             :                         resolved, resolved_components, mnt_components);
     326             :                 // set matched counter to value coherent with the new path
     327           0 :                 last_slash_pos = resolved.find_last_of(path::separator);
     328           0 :                 continue;
     329           0 :             } else if((!S_ISDIR(st.st_mode)) && (end != path.size())) {
     330           0 :                 resolved.append(path, end, string::npos);
     331           0 :                 return false;
     332             :             }
     333             :         } else {
     334             :             // Inside GekkoFS
     335           0 :             ++matched_components;
     336             :         }
     337           0 :         ++resolved_components;
     338             :     }
     339             : 
     340           0 :     if(matched_components >= mnt_components.size()) {
     341           0 :         resolved.erase(1, CTX->mountdir().size());
     342           0 :         LOG(DEBUG, "internal: \"{}\"", resolved);
     343           0 :         return true;
     344             :     }
     345             : 
     346           0 :     if(resolved.empty()) {
     347           0 :         resolved.push_back(path::separator);
     348             :     }
     349           0 :     LOG(DEBUG, "external: \"{}\"", resolved);
     350             :     return false;
     351             : }
     352             : 
     353             : string
     354         270 : get_sys_cwd() {
     355         270 :     char temp[path::max_length];
     356         540 :     if(long ret =
     357         270 :                syscall_no_intercept(SYS_getcwd, temp, path::max_length) < 0) {
     358           0 :         throw ::system_error(syscall_error_code(ret), ::system_category(),
     359           0 :                              "Failed to retrieve current working directory");
     360             :     }
     361             :     // getcwd could return "(unreachable)<PATH>" in some cases
     362         270 :     if(temp[0] != path::separator) {
     363           0 :         throw ::runtime_error("Current working directory is unreachable");
     364             :     }
     365         270 :     return {temp};
     366             : }
     367             : 
     368             : void
     369           8 : set_sys_cwd(const string& path) {
     370             : 
     371           8 :     LOG(DEBUG, "Changing working directory to \"{}\"", path);
     372             : 
     373           8 :     if(long ret = syscall_no_intercept(SYS_chdir, path.c_str())) {
     374           2 :         LOG(ERROR, "Failed to change working directory: {}",
     375           1 :             ::strerror(syscall_error_code(ret)));
     376           2 :         throw ::system_error(syscall_error_code(ret), ::system_category(),
     377           2 :                              "Failed to set system current working directory");
     378             :     }
     379           7 : }
     380             : 
     381             : void
     382           4 : set_env_cwd(const string& path) {
     383             : 
     384           4 :     LOG(DEBUG, "Setting {} to \"{}\"", gkfs::env::CWD, path);
     385             : 
     386           4 :     if(setenv(gkfs::env::CWD, path.c_str(), 1)) {
     387           0 :         LOG(ERROR, "Failed while setting {}: {}", gkfs::env::CWD,
     388           0 :             ::strerror(errno));
     389           0 :         throw ::system_error(
     390           0 :                 errno, ::system_category(),
     391           0 :                 "Failed to set environment current working directory");
     392             :     }
     393           4 : }
     394             : 
     395             : void
     396           4 : unset_env_cwd() {
     397             : 
     398           4 :     LOG(DEBUG, "Clearing {}()", gkfs::env::CWD);
     399             : 
     400           4 :     if(unsetenv(gkfs::env::CWD)) {
     401             : 
     402           0 :         LOG(ERROR, "Failed to clear {}: {}", gkfs::env::CWD, ::strerror(errno));
     403             : 
     404           0 :         throw ::system_error(
     405           0 :                 errno, ::system_category(),
     406           0 :                 "Failed to unset environment current working directory");
     407             :     }
     408           4 : }
     409             : 
     410             : void
     411         269 : init_cwd() {
     412         269 :     const char* env_cwd = ::getenv(gkfs::env::CWD);
     413         269 :     if(env_cwd != nullptr) {
     414           0 :         CTX->cwd(env_cwd);
     415             :     } else {
     416         538 :         CTX->cwd(get_sys_cwd());
     417             :     }
     418         269 : }
     419             : 
     420             : void
     421           8 : set_cwd(const string& path, bool internal) {
     422           8 :     if(internal) {
     423           4 :         set_sys_cwd(CTX->mountdir());
     424           4 :         set_env_cwd(path);
     425             :     } else {
     426           4 :         set_sys_cwd(path);
     427           3 :         unset_env_cwd();
     428             :     }
     429           7 :     CTX->cwd(path);
     430           7 : }
     431             : 
     432             : } // namespace gkfs::path

Generated by: LCOV version 1.16