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
|