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 <vector>
38 : #include <string>
39 : #include <cassert>
40 : #include <climits>
41 :
42 : extern "C" {
43 : #include <sys/stat.h>
44 : #include <libsyscall_intercept_hook_point.h>
45 : }
46 :
47 : using namespace std;
48 :
49 : namespace gkfs::path {
50 :
51 : static const string excluded_paths[2] = {"sys/", "proc/"};
52 :
53 : /** Match components in path
54 : *
55 : * Returns the number of consecutive components at start of `path`
56 : * that match the ones in `components` vector.
57 : *
58 : * `path_components` will be set to the total number of components found in
59 : * `path`
60 : *
61 : * Example:
62 : * ```ÏÏ
63 : * unsigned int tot_comp;
64 : * path_match_components("/matched/head/with/tail", &tot_comp, ["matched",
65 : * "head", "no"]) == 2; tot_comp == 4;
66 : * ```
67 : */
68 : unsigned int
69 3 : match_components(const string& path, unsigned int& path_components,
70 : const ::vector<string>& components) {
71 3 : unsigned int matched = 0;
72 3 : unsigned int processed_components = 0;
73 3 : string::size_type comp_size = 0; // size of current component
74 3 : string::size_type start = 0; // start index of curr component
75 3 : string::size_type end = 0; // end index of curr component (last processed
76 : // Path Separator "separator")
77 :
78 9 : while(++end < path.size()) {
79 6 : start = end;
80 :
81 : // Find next component
82 6 : end = path.find(path::separator, start);
83 6 : if(end == string::npos) {
84 3 : end = path.size();
85 : }
86 :
87 6 : comp_size = end - start;
88 9 : if(matched == processed_components &&
89 3 : path.compare(start, comp_size, components.at(matched)) == 0) {
90 0 : ++matched;
91 : }
92 6 : ++processed_components;
93 : }
94 3 : path_components = processed_components;
95 3 : return matched;
96 : }
97 :
98 : /** Resolve path to its canonical representation
99 : *
100 : * Populate `resolved` with the canonical representation of `path`.
101 : *
102 : * ".", ".." and symbolic links gets resolved.
103 : *
104 : * If `resolve_last_link` is false, the last components in path
105 : * won't be resolved if its a link.
106 : *
107 : * returns true if the resolved path fall inside GekkoFS namespace,
108 : * and false otherwise.
109 : */
110 : bool
111 9201 : resolve(const string& path, string& resolved, bool resolve_last_link) {
112 :
113 9201 : LOG(DEBUG, "path: \"{}\", resolved: \"{}\", resolve_last_link: {}", path,
114 9201 : resolved, resolve_last_link);
115 :
116 9201 : assert(path::is_absolute(path));
117 :
118 27603 : for(auto& excl_path : excluded_paths) {
119 18402 : if(path.compare(1, excl_path.length(), excl_path) == 0) {
120 0 : LOG(DEBUG, "Skipping: '{}'", path);
121 0 : resolved = path;
122 0 : return false;
123 : }
124 : }
125 :
126 9201 : struct stat st {};
127 9201 : const ::vector<string>& mnt_components = CTX->mountdir_components();
128 9201 : unsigned int matched_components =
129 : 0; // matched number of component in mountdir
130 9201 : unsigned int resolved_components = 0;
131 9201 : string::size_type comp_size = 0; // size of current component
132 9201 : string::size_type start = 0; // start index of curr component
133 9201 : string::size_type end = 0; // end index of curr component (last processed
134 : // Path Separator "separator")
135 9201 : string::size_type last_slash_pos =
136 : 0; // index of last slash in resolved path
137 9201 : resolved.clear();
138 9201 : resolved.reserve(path.size());
139 :
140 129529 : while(++end < path.size()) {
141 : start = end;
142 :
143 : /* Skip sequence of multiple path-separators. */
144 120557 : while(start < path.size() && path[start] == path::separator) {
145 0 : ++start;
146 : }
147 :
148 : // Find next component
149 120557 : end = path.find(path::separator, start);
150 120557 : if(end == string::npos) {
151 9201 : end = path.size();
152 : }
153 120557 : comp_size = end - start;
154 :
155 120557 : if(comp_size == 0) {
156 : // component is empty (this must be the last component)
157 : break;
158 : }
159 120557 : if(comp_size == 1 && path.at(start) == '.') {
160 : // component is '.', we skip it
161 1 : continue;
162 : }
163 120556 : if(comp_size == 2 && path.at(start) == '.' &&
164 98 : path.at(start + 1) == '.') {
165 : // component is '..' we need to rollback resolved path
166 98 : if(!resolved.empty()) {
167 49 : resolved.erase(last_slash_pos);
168 : /* TODO Optimization
169 : * the previous slash position should be stored.
170 : * The following search could be avoided.
171 : */
172 49 : last_slash_pos = resolved.find_last_of(path::separator);
173 : }
174 98 : if(resolved_components > 0) {
175 49 : if(matched_components == resolved_components) {
176 40 : --matched_components;
177 : }
178 49 : --resolved_components;
179 : }
180 98 : continue;
181 : }
182 :
183 : // add `/<component>` to the reresolved path
184 120458 : resolved.push_back(path::separator);
185 120458 : last_slash_pos = resolved.size() - 1;
186 120458 : resolved.append(path, start, comp_size);
187 :
188 120458 : if(matched_components < mnt_components.size()) {
189 : // Outside GekkoFS
190 196490 : if(matched_components == resolved_components &&
191 78346 : path.compare(start, comp_size,
192 78346 : mnt_components.at(matched_components)) == 0) {
193 70412 : ++matched_components;
194 : }
195 118144 : if(lstat(resolved.c_str(), &st) < 0) {
196 :
197 229 : LOG(DEBUG, "path \"{}\" does not exist", resolved);
198 :
199 229 : resolved.append(path, end, string::npos);
200 229 : return false;
201 : }
202 117915 : if(S_ISLNK(st.st_mode)) {
203 3 : if(!resolve_last_link && end == path.size()) {
204 0 : continue;
205 : }
206 3 : auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]);
207 3 : if(realpath(resolved.c_str(), link_resolved.get()) == nullptr) {
208 :
209 0 : LOG(ERROR,
210 : "Failed to get realpath for link \"{}\". "
211 : "Error: {}",
212 0 : resolved, ::strerror(errno));
213 :
214 0 : resolved.append(path, end, string::npos);
215 0 : return false;
216 : }
217 : // substituute resolved with new link path
218 3 : resolved = link_resolved.get();
219 3 : matched_components = match_components(
220 : resolved, resolved_components, mnt_components);
221 : // set matched counter to value coherent with the new path
222 3 : last_slash_pos = resolved.find_last_of(path::separator);
223 6 : continue;
224 117912 : } else if((!S_ISDIR(st.st_mode)) && (end != path.size())) {
225 0 : resolved.append(path, end, string::npos);
226 0 : return false;
227 : }
228 : } else {
229 : // Inside GekkoFS
230 2314 : ++matched_components;
231 : }
232 120226 : ++resolved_components;
233 : }
234 :
235 8972 : if(matched_components >= mnt_components.size()) {
236 1271 : resolved.erase(1, CTX->mountdir().size());
237 1271 : LOG(DEBUG, "internal: \"{}\"", resolved);
238 1271 : return true;
239 : }
240 :
241 7701 : if(resolved.empty()) {
242 0 : resolved.push_back(path::separator);
243 : }
244 7701 : LOG(DEBUG, "external: \"{}\"", resolved);
245 : return false;
246 : }
247 :
248 : string
249 249 : get_sys_cwd() {
250 249 : char temp[path::max_length];
251 498 : if(long ret =
252 249 : syscall_no_intercept(SYS_getcwd, temp, path::max_length) < 0) {
253 0 : throw ::system_error(syscall_error_code(ret), ::system_category(),
254 0 : "Failed to retrieve current working directory");
255 : }
256 : // getcwd could return "(unreachable)<PATH>" in some cases
257 249 : if(temp[0] != path::separator) {
258 0 : throw ::runtime_error("Current working directory is unreachable");
259 : }
260 249 : return {temp};
261 : }
262 :
263 : void
264 8 : set_sys_cwd(const string& path) {
265 :
266 8 : LOG(DEBUG, "Changing working directory to \"{}\"", path);
267 :
268 8 : if(long ret = syscall_no_intercept(SYS_chdir, path.c_str())) {
269 2 : LOG(ERROR, "Failed to change working directory: {}",
270 1 : ::strerror(syscall_error_code(ret)));
271 2 : throw ::system_error(syscall_error_code(ret), ::system_category(),
272 2 : "Failed to set system current working directory");
273 : }
274 7 : }
275 :
276 : void
277 4 : set_env_cwd(const string& path) {
278 :
279 4 : LOG(DEBUG, "Setting {} to \"{}\"", gkfs::env::CWD, path);
280 :
281 4 : if(setenv(gkfs::env::CWD, path.c_str(), 1)) {
282 0 : LOG(ERROR, "Failed while setting {}: {}", gkfs::env::CWD,
283 0 : ::strerror(errno));
284 0 : throw ::system_error(
285 0 : errno, ::system_category(),
286 0 : "Failed to set environment current working directory");
287 : }
288 4 : }
289 :
290 : void
291 4 : unset_env_cwd() {
292 :
293 4 : LOG(DEBUG, "Clearing {}()", gkfs::env::CWD);
294 :
295 4 : if(unsetenv(gkfs::env::CWD)) {
296 :
297 0 : LOG(ERROR, "Failed to clear {}: {}", gkfs::env::CWD, ::strerror(errno));
298 :
299 0 : throw ::system_error(
300 0 : errno, ::system_category(),
301 0 : "Failed to unset environment current working directory");
302 : }
303 4 : }
304 :
305 : void
306 248 : init_cwd() {
307 248 : const char* env_cwd = ::getenv(gkfs::env::CWD);
308 248 : if(env_cwd != nullptr) {
309 0 : CTX->cwd(env_cwd);
310 : } else {
311 496 : CTX->cwd(get_sys_cwd());
312 : }
313 248 : }
314 :
315 : void
316 8 : set_cwd(const string& path, bool internal) {
317 8 : if(internal) {
318 4 : set_sys_cwd(CTX->mountdir());
319 4 : set_env_cwd(path);
320 : } else {
321 4 : set_sys_cwd(path);
322 3 : unset_env_cwd();
323 : }
324 7 : CTX->cwd(path);
325 7 : }
326 :
327 : } // namespace gkfs::path
|