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/preload_context.hpp>
31 : #include <client/env.hpp>
32 : #include <client/logging.hpp>
33 : #include <client/open_file_map.hpp>
34 : #include <client/open_dir.hpp>
35 : #include <client/path.hpp>
36 :
37 : #include <common/env_util.hpp>
38 : #include <common/path_util.hpp>
39 : #include <config.hpp>
40 :
41 : #include <hermes.hpp>
42 :
43 : #include <cassert>
44 :
45 : extern "C" {
46 : #include <libsyscall_intercept_hook_point.h>
47 : #include <syscall.h>
48 : }
49 :
50 : namespace gkfs {
51 :
52 : namespace preload {
53 :
54 : decltype(PreloadContext::MIN_INTERNAL_FD) constexpr PreloadContext::
55 : MIN_INTERNAL_FD;
56 : decltype(PreloadContext::MAX_USER_FDS) constexpr PreloadContext::MAX_USER_FDS;
57 :
58 248 : PreloadContext::PreloadContext()
59 : : ofm_(std::make_shared<gkfs::filemap::OpenFileMap>()),
60 744 : fs_conf_(std::make_shared<FsConfig>()) {
61 :
62 248 : internal_fds_.set();
63 248 : internal_fds_must_relocate_ = true;
64 :
65 248 : char host[255];
66 248 : gethostname(host, 255);
67 248 : hostname = host;
68 496 : PreloadContext::set_replicas(
69 496 : std::stoi(gkfs::env::get_var(gkfs::env::NUM_REPL, "0")));
70 248 : }
71 :
72 : void
73 248 : PreloadContext::init_logging() {
74 :
75 248 : const std::string log_opts = gkfs::env::get_var(
76 496 : gkfs::env::LOG, gkfs::config::log::client_log_level);
77 :
78 248 : const std::string log_output = gkfs::env::get_var(
79 744 : gkfs::env::LOG_OUTPUT, gkfs::config::log::client_log_path);
80 :
81 248 : const bool log_per_process =
82 496 : gkfs::env::get_var(gkfs::env::LOG_PER_PROCESS).empty() ? false
83 248 : : true;
84 :
85 : #ifdef GKFS_DEBUG_BUILD
86 : // atoi returns 0 if no int conversion can be performed, which works
87 : // for us since if the user provides a non-numeric value we can just treat
88 : // it as zero
89 496 : const int log_verbosity = std::atoi(
90 496 : gkfs::env::get_var(gkfs::env::LOG_DEBUG_VERBOSITY, "0").c_str());
91 :
92 248 : const std::string log_filter =
93 744 : gkfs::env::get_var(gkfs::env::LOG_SYSCALL_FILTER, "");
94 : #endif
95 :
96 248 : const std::string trunc_val =
97 744 : gkfs::env::get_var(gkfs::env::LOG_OUTPUT_TRUNC);
98 :
99 248 : const bool log_trunc = (!trunc_val.empty() && trunc_val[0] != '0');
100 :
101 248 : gkfs::log::create_global_logger(log_opts, log_output, log_per_process,
102 : log_trunc
103 : #ifdef GKFS_DEBUG_BUILD
104 : ,
105 : log_filter, log_verbosity
106 : #endif
107 : );
108 248 : }
109 :
110 : void
111 248 : PreloadContext::mountdir(const std::string& path) {
112 248 : assert(gkfs::path::is_absolute(path));
113 248 : assert(!gkfs::path::has_trailing_slash(path));
114 248 : mountdir_components_ = gkfs::path::split_path(path);
115 248 : mountdir_ = path;
116 248 : }
117 :
118 : const std::string&
119 1527 : PreloadContext::mountdir() const {
120 1527 : return mountdir_;
121 : }
122 :
123 : const std::vector<std::string>&
124 9201 : PreloadContext::mountdir_components() const {
125 9201 : return mountdir_components_;
126 : }
127 :
128 : void
129 256 : PreloadContext::cwd(const std::string& path) {
130 256 : cwd_ = path;
131 256 : }
132 :
133 : const std::string&
134 263 : PreloadContext::cwd() const {
135 263 : return cwd_;
136 : }
137 :
138 : const std::vector<hermes::endpoint>&
139 3415 : PreloadContext::hosts() const {
140 3415 : return hosts_;
141 : }
142 :
143 : void
144 248 : PreloadContext::hosts(const std::vector<hermes::endpoint>& endpoints) {
145 248 : hosts_ = endpoints;
146 248 : }
147 :
148 : void
149 248 : PreloadContext::clear_hosts() {
150 248 : hosts_.clear();
151 248 : }
152 :
153 : uint64_t
154 496 : PreloadContext::local_host_id() const {
155 496 : return local_host_id_;
156 : }
157 :
158 : void
159 248 : PreloadContext::local_host_id(uint64_t id) {
160 248 : local_host_id_ = id;
161 248 : }
162 :
163 : uint64_t
164 0 : PreloadContext::fwd_host_id() const {
165 0 : return fwd_host_id_;
166 : }
167 :
168 : void
169 0 : PreloadContext::fwd_host_id(uint64_t id) {
170 0 : fwd_host_id_ = id;
171 0 : }
172 :
173 : const std::string&
174 496 : PreloadContext::rpc_protocol() const {
175 496 : return rpc_protocol_;
176 : }
177 :
178 : void
179 248 : PreloadContext::rpc_protocol(const std::string& rpc_protocol) {
180 248 : rpc_protocol_ = rpc_protocol;
181 248 : }
182 :
183 : bool
184 496 : PreloadContext::auto_sm() const {
185 496 : return auto_sm_;
186 : }
187 :
188 : void
189 0 : PreloadContext::auto_sm(bool auto_sm) {
190 0 : PreloadContext::auto_sm_ = auto_sm;
191 0 : }
192 :
193 : RelativizeStatus
194 9129 : PreloadContext::relativize_fd_path(int dirfd, const char* raw_path,
195 : std::string& relative_path, int flags,
196 : bool resolve_last_link) const {
197 :
198 : // Relativize path should be called only after the library constructor has
199 : // been executed
200 9129 : assert(interception_enabled_);
201 : // If we run the constructor we also already setup the mountdir
202 9129 : assert(!mountdir_.empty());
203 :
204 : // We assume raw path is valid
205 9129 : assert(raw_path != nullptr);
206 :
207 18258 : std::string path;
208 :
209 9129 : if(raw_path != nullptr && raw_path[0] != gkfs::path::separator) {
210 : // path is relative
211 3 : if(dirfd == AT_FDCWD) {
212 : // path is relative to cwd
213 3 : path = gkfs::path::prepend_path(cwd_, raw_path);
214 : } else {
215 0 : if(!ofm_->exist(dirfd)) {
216 0 : return RelativizeStatus::fd_unknown;
217 : } else {
218 : // check if we have the AT_EMPTY_PATH flag
219 : // for fstatat.
220 0 : if(flags & AT_EMPTY_PATH) {
221 0 : relative_path = ofm_->get(dirfd)->path();
222 0 : return RelativizeStatus::internal;
223 : }
224 : }
225 : // path is relative to fd
226 0 : auto dir = ofm_->get_dir(dirfd);
227 0 : if(dir == nullptr) {
228 0 : return RelativizeStatus::fd_not_a_dir;
229 : }
230 0 : path = mountdir_;
231 0 : path.append(dir->path());
232 0 : path.push_back(gkfs::path::separator);
233 0 : path.append(raw_path);
234 : }
235 : } else {
236 9126 : path = raw_path;
237 : }
238 :
239 9129 : if(gkfs::path::resolve(path, relative_path, resolve_last_link)) {
240 1207 : return RelativizeStatus::internal;
241 : }
242 : return RelativizeStatus::external;
243 : }
244 :
245 : bool
246 72 : PreloadContext::relativize_path(const char* raw_path,
247 : std::string& relative_path,
248 : bool resolve_last_link) const {
249 : // Relativize path should be called only after the library constructor has
250 : // been executed
251 72 : assert(interception_enabled_);
252 : // If we run the constructor we also already setup the mountdir
253 72 : assert(!mountdir_.empty());
254 :
255 : // We assume raw path is valid
256 72 : assert(raw_path != nullptr);
257 :
258 72 : std::string path;
259 :
260 72 : if(raw_path != nullptr && raw_path[0] != gkfs::path::separator) {
261 : /* Path is not absolute, we need to prepend CWD;
262 : * First reserve enough space to minimize memory copy
263 : */
264 4 : path = gkfs::path::prepend_path(cwd_, raw_path);
265 : } else {
266 68 : path = raw_path;
267 : }
268 :
269 72 : return gkfs::path::resolve(path, relative_path, resolve_last_link);
270 : }
271 :
272 : const std::shared_ptr<gkfs::filemap::OpenFileMap>&
273 373254 : PreloadContext::file_map() const {
274 373254 : return ofm_;
275 : }
276 :
277 : void
278 248 : PreloadContext::distributor(std::shared_ptr<gkfs::rpc::Distributor> d) {
279 248 : distributor_ = d;
280 248 : }
281 :
282 : std::shared_ptr<gkfs::rpc::Distributor>
283 2716 : PreloadContext::distributor() const {
284 2716 : return distributor_;
285 : }
286 :
287 : const std::shared_ptr<FsConfig>&
288 2355 : PreloadContext::fs_conf() const {
289 2355 : return fs_conf_;
290 : }
291 :
292 : void
293 496 : PreloadContext::enable_interception() {
294 496 : interception_enabled_ = true;
295 496 : }
296 :
297 : void
298 248 : PreloadContext::disable_interception() {
299 248 : interception_enabled_ = false;
300 248 : }
301 :
302 : bool
303 11351081 : PreloadContext::interception_enabled() const {
304 11351081 : return interception_enabled_;
305 : }
306 :
307 : int
308 13640 : PreloadContext::register_internal_fd(int fd) {
309 :
310 13640 : assert(fd >= 0);
311 :
312 13640 : if(!internal_fds_must_relocate_) {
313 7688 : LOG(DEBUG, "registering fd {} as internal (no relocation needed)", fd);
314 7688 : assert(fd >= MIN_INTERNAL_FD);
315 7688 : internal_fds_.reset(fd - MIN_INTERNAL_FD);
316 7688 : return fd;
317 : }
318 :
319 5952 : LOG(DEBUG, "registering fd {} as internal (needs relocation)", fd);
320 :
321 19592 : std::lock_guard<std::mutex> lock(internal_fds_mutex_);
322 5952 : const int pos = internal_fds_._Find_first();
323 :
324 5952 : if(static_cast<std::size_t>(pos) == internal_fds_.size()) {
325 0 : throw std::runtime_error(
326 : "Internal GekkoFS file descriptors exhausted, increase GKFS_MAX_INTERNAL_FDS in "
327 0 : "CMake, rebuild GekkoFS and try again.");
328 : }
329 5952 : internal_fds_.reset(pos);
330 :
331 :
332 : #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD)
333 5952 : long args[gkfs::syscall::MAX_ARGS]{fd, pos + MIN_INTERNAL_FD, O_CLOEXEC};
334 : #endif
335 :
336 5952 : LOG(SYSCALL,
337 : gkfs::syscall::from_internal_code | gkfs::syscall::to_kernel |
338 : gkfs::syscall::not_executed,
339 5952 : SYS_dup3, args);
340 :
341 5952 : const int ifd = ::syscall_no_intercept(SYS_dup3, fd, pos + MIN_INTERNAL_FD,
342 5952 : O_CLOEXEC);
343 :
344 5952 : LOG(SYSCALL,
345 : gkfs::syscall::from_internal_code | gkfs::syscall::to_kernel |
346 : gkfs::syscall::executed,
347 5952 : SYS_dup3, args, ifd);
348 :
349 5952 : assert(::syscall_error_code(ifd) == 0);
350 :
351 : #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD)
352 5952 : long args2[gkfs::syscall::MAX_ARGS]{fd};
353 : #endif
354 :
355 5952 : LOG(SYSCALL,
356 : gkfs::syscall::from_internal_code | gkfs::syscall::to_kernel |
357 : gkfs::syscall::not_executed,
358 5952 : SYS_close, args2);
359 :
360 : #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD)
361 5952 : int rv = ::syscall_no_intercept(SYS_close, fd);
362 : #else
363 : ::syscall_no_intercept(SYS_close, fd);
364 : #endif
365 :
366 5952 : LOG(SYSCALL,
367 : gkfs::syscall::from_internal_code | gkfs::syscall::to_kernel |
368 : gkfs::syscall::executed,
369 5952 : SYS_close, args2, rv);
370 :
371 5952 : LOG(DEBUG, " (fd {} relocated to ifd {})", fd, ifd);
372 :
373 5952 : return ifd;
374 : }
375 :
376 : void
377 9424 : PreloadContext::unregister_internal_fd(int fd) {
378 :
379 9424 : LOG(DEBUG, "unregistering internal fd {}", fd);
380 :
381 9424 : assert(fd >= MIN_INTERNAL_FD);
382 :
383 9424 : const auto pos = fd - MIN_INTERNAL_FD;
384 :
385 9424 : std::lock_guard<std::mutex> lock(internal_fds_mutex_);
386 9424 : internal_fds_.set(pos);
387 9424 : }
388 :
389 : bool
390 10853 : PreloadContext::is_internal_fd(int fd) const {
391 :
392 10853 : if(fd < MIN_INTERNAL_FD) {
393 : return false;
394 : }
395 :
396 2946 : const auto pos = fd - MIN_INTERNAL_FD;
397 :
398 13799 : std::lock_guard<std::mutex> lock(internal_fds_mutex_);
399 2946 : return !internal_fds_.test(pos);
400 : }
401 :
402 : void
403 248 : PreloadContext::protect_user_fds() {
404 :
405 248 : LOG(DEBUG, "Protecting application fds [{}, {}]", 0, MAX_USER_FDS - 1);
406 :
407 248 : const int nullfd =
408 248 : ::syscall_no_intercept(SYS_openat, 0, "/dev/null", O_RDONLY);
409 248 : assert(::syscall_error_code(nullfd) == 0);
410 248 : protected_fds_.set(nullfd);
411 :
412 2226296 : const auto fd_is_open = [](int fd) -> bool {
413 2226048 : const int ret = ::syscall_no_intercept(SYS_fcntl, fd, F_GETFD);
414 2226048 : const int error = ::syscall_error_code(ret);
415 2226048 : return error == 0 || error != EBADF;
416 : };
417 :
418 2226296 : for(int fd = 0; fd < MAX_USER_FDS; ++fd) {
419 2226048 : if(fd_is_open(fd)) {
420 992 : LOG(DEBUG, " fd {} was already in use, skipping", fd);
421 992 : continue;
422 : }
423 :
424 2225056 : const int ret = ::syscall_no_intercept(SYS_dup3, nullfd, fd, O_CLOEXEC);
425 2225056 : assert(::syscall_error_code(ret) == 0);
426 2225056 : protected_fds_.set(fd);
427 : }
428 :
429 248 : internal_fds_must_relocate_ = false;
430 248 : }
431 :
432 : void
433 248 : PreloadContext::unprotect_user_fds() {
434 :
435 2226296 : for(std::size_t fd = 0; fd < protected_fds_.size(); ++fd) {
436 2226048 : if(!protected_fds_[fd]) {
437 744 : continue;
438 : }
439 :
440 2225304 : const int ret =
441 2226048 : ::syscall_error_code(::syscall_no_intercept(SYS_close, fd));
442 :
443 0 : if(ret != 0) {
444 0 : LOG(ERROR, "Failed to unprotect fd")
445 : }
446 : }
447 :
448 248 : internal_fds_must_relocate_ = true;
449 248 : }
450 :
451 :
452 : std::string
453 28 : PreloadContext::get_hostname() {
454 28 : return hostname;
455 : }
456 :
457 : void
458 248 : PreloadContext::set_replicas(const int repl) {
459 248 : replicas_ = repl;
460 248 : }
461 :
462 : int
463 2720 : PreloadContext::get_replicas() {
464 2720 : return replicas_;
465 : }
466 :
467 : } // namespace preload
468 : } // namespace gkfs
|