Commit b513ff43 authored by Ramon Nou's avatar Ramon Nou
Browse files

feat(client): intercept posix_spawn and posix_spawnp for virtual directories

parent f51bed4d
Loading
Loading
Loading
Loading
Loading
+16 −0
Changes for include/client/gkfs_libc.hpp: 16 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@
*/

#ifdef __cplusplus
#include <filesystem>
extern "C" {
#endif

@@ -50,6 +51,7 @@ extern "C" {

#include <sys/types.h>
#include <dirent.h>
#include <spawn.h>


#ifndef _STAT_VER
@@ -349,6 +351,20 @@ ssize_t
flistxattr(int fd, char* list, size_t size);
#endif

//------------------------- Process Spawning ---------------------------------//

int
posix_spawn(pid_t* __restrict pid, const char* __restrict path,
            const posix_spawn_file_actions_t* __restrict file_actions,
            const posix_spawnattr_t* __restrict attrp,
            char* const argv[], char* const envp[]);

int
posix_spawnp(pid_t* __restrict pid, const char* __restrict file,
             const posix_spawn_file_actions_t* __restrict file_actions,
             const posix_spawnattr_t* __restrict attrp,
             char* const argv[], char* const envp[]);

//========================= C++ ABI Interceptions
//==============================//
// Intercepting C++ std::filesystem::remove_all
+2 −0
Changes for src/client/CMakeLists.txt: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ target_sources(gkfs_intercept
    preload.cpp
    preload_context.cpp
    preload_util.cpp
    posix_spawn.cpp
)

if (GKFS_BUILD_USER_LIB)
@@ -113,6 +114,7 @@ target_sources(
    preload.cpp
    preload_context.cpp
    preload_util.cpp
    posix_spawn.cpp
)
endif ()

+12 −0
Changes for src/client/gkfs_libc.cpp: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -2460,6 +2460,18 @@ faccessat(int dfd, const char* path, int mode, int flags) throw() {
int
chdir(const char* path) throw() {
    gkfs_init_routine_placeholder();
    if(path != nullptr && CTX && !CTX->mountdir().empty()) {
        std::string p(path);
        if(p.rfind(CTX->mountdir(), 0) == 0 && !GKFS_INTERCEPTION_ENABLED()) {
            long cres = syscall_no_intercept(SYS_chdir, CTX->mountdir().c_str());
            if(cres < 0) {
                syscall_no_intercept(SYS_chdir, "/");
            }
            setenv(gkfs::env::CWD, p.c_str(), 1);
            CTX->cwd(p);
            return 0;
        }
    }
    if(GKFS_INTERCEPTION_ENABLED()) {
        std::string resolved;
        // For chdir, path must be a directory. GekkoFS must update its CWD.
+31 −0
Changes for src/client/intercept.cpp: 31 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1396,6 +1396,37 @@ internal_hook_guard_wrapper(long syscall_number, long arg0, long arg1,
                            long arg2, long arg3, long arg4, long arg5,
                            long* syscall_return_value) {
    if(!CTX->interception_enabled()) {
        if(syscall_number == SYS_chdir) {
            const char* path = reinterpret_cast<const char*>(arg0);
            if(path != nullptr && !CTX->mountdir().empty()) {
                std::string p(path);
                if(p.rfind(CTX->mountdir(), 0) == 0) {
                    long cres = syscall_no_intercept_wrapper(
                            SYS_chdir, CTX->mountdir().c_str());
                    if(cres < 0) {
                        syscall_no_intercept_wrapper(SYS_chdir, "/");
                    }
                    setenv(gkfs::env::CWD, p.c_str(), 1);
                    CTX->cwd(p);
                    *syscall_return_value = 0;
                    return gkfs::syscall::hooked;
                }
            }
        } else if(syscall_number == SYS_getcwd) {
            char* buf = reinterpret_cast<char*>(arg0);
            unsigned long size = static_cast<unsigned long>(arg1);
            if(buf != nullptr && size > 0 && !CTX->cwd().empty() &&
               !CTX->mountdir().empty() &&
               CTX->cwd().rfind(CTX->mountdir(), 0) == 0) {
                if(CTX->cwd().size() + 1 > size) {
                    *syscall_return_value = -ERANGE;
                } else {
                    strcpy(buf, CTX->cwd().c_str());
                    *syscall_return_value = CTX->cwd().size() + 1;
                }
                return gkfs::syscall::hooked;
            }
        }
        return gkfs::syscall::forward_to_kernel;
    }

+522 −0
Changes for src/client/posix_spawn.cpp: 522 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
  Copyright 2018-2026, Barcelona Supercomputing Center (BSC), Spain
  Copyright 2015-2026, 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 software was partially supported by the
  the European Union’s Horizon 2020 JTI-EuroHPC research and
  innovation programme, by the project ADMIRE (Project ID: 956748,
  admire-eurohpc.eu)

  This project was partially promoted by the Ministry for Digital Transformation
  and the Civil Service, within the framework of the Recovery,
  Transformation and Resilience Plan - Funded by the European Union
  -NextGenerationEU.

  This file is part of GekkoFS' POSIX interface.

  GekkoFS' POSIX interface is free software: you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation, either version 3 of the License,
  or (at your option) any later version.

  This software 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with GekkoFS' POSIX interface.  If not, see
  <https://www.gnu.org/licenses/>.

  SPDX-License-Identifier: LGPL-3.0-or-later
*/

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <client/hooks.hpp>
#include <client/logging.hpp>
#include <client/preload.hpp>
#include <client/preload_context.hpp>
#include <common/env_util.hpp>

#include <csignal>
#include <dlfcn.h>
#include <fcntl.h>
#include <sched.h>
#include <spawn.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>

#include <string>
#include <vector>

extern char** environ;

namespace {

static int (*real_posix_spawn)(
        pid_t* __restrict, const char* __restrict,
        const posix_spawn_file_actions_t* __restrict,
        const posix_spawnattr_t* __restrict, char* const[],
        char* const[]) = nullptr;

static int (*real_posix_spawnp)(
        pid_t* __restrict, const char* __restrict,
        const posix_spawn_file_actions_t* __restrict,
        const posix_spawnattr_t* __restrict, char* const[],
        char* const[]) = nullptr;

int
dlsym_posix_spawn(pid_t* __restrict pid, const char* __restrict path,
                  const posix_spawn_file_actions_t* __restrict file_actions,
                  const posix_spawnattr_t* __restrict attrp, char* const argv[],
                  char* const envp[]) {
    if(!real_posix_spawn) {
        real_posix_spawn = reinterpret_cast<int (*)(
                pid_t* __restrict, const char* __restrict,
                const posix_spawn_file_actions_t* __restrict,
                const posix_spawnattr_t* __restrict, char* const[],
                char* const[])>(dlsym(RTLD_NEXT, "posix_spawn"));
        if(!real_posix_spawn) {
            LOG(ERROR, "dlsym failed for posix_spawn: {}", dlerror());
            errno = ENOSYS;
            return -1;
        }
    }
    return real_posix_spawn(pid, path, file_actions, attrp, argv, envp);
}

int
dlsym_posix_spawnp(pid_t* __restrict pid, const char* __restrict file,
                   const posix_spawn_file_actions_t* __restrict file_actions,
                   const posix_spawnattr_t* __restrict attrp,
                   char* const argv[], char* const envp[]) {
    if(!real_posix_spawnp) {
        real_posix_spawnp = reinterpret_cast<int (*)(
                pid_t* __restrict, const char* __restrict,
                const posix_spawn_file_actions_t* __restrict,
                const posix_spawnattr_t* __restrict, char* const[],
                char* const[])>(dlsym(RTLD_NEXT, "posix_spawnp"));
        if(!real_posix_spawnp) {
            LOG(ERROR, "dlsym failed for posix_spawnp: {}", dlerror());
            errno = ENOSYS;
            return -1;
        }
    }
    return real_posix_spawnp(pid, file, file_actions, attrp, argv, envp);
}

struct gkfs_spawn_action {
    enum {
        spawn_do_close,
        spawn_do_dup2,
        spawn_do_open,
        spawn_do_chdir,
        spawn_do_fchdir,
        spawn_do_closefrom,
        spawn_do_tcsetpgrp
    } tag;

    union {
        struct {
            int fd;
        } close_action;
        struct {
            int fd;
            int newfd;
        } dup2_action;
        struct {
            int fd;
            const char* path;
            int oflag;
            mode_t mode;
        } open_action;
        struct {
            const char* path;
        } chdir_action;
        struct {
            int fd;
        } fchdir_action;
        struct {
            int lowfd;
        } closefrom_action;
        struct {
            pid_t pgrp;
        } tcsetpgrp_action;
    } action;
};

struct gkfs_posix_spawn_file_actions {
    int __allocated;
    int __used;
    struct gkfs_spawn_action* __actions;
    int __pad[16];
};

bool
posix_spawn_involves_gkfs(const char* file_or_path,
                          const posix_spawn_file_actions_t* file_actions) {
    if(!CTX || !CTX->interception_enabled()) {
        return false;
    }

    const std::string& mountdir = CTX->mountdir();
    if(!mountdir.empty()) {
        const std::string& cwd = CTX->cwd();
        if(cwd.rfind(mountdir, 0) == 0) {
            return true;
        }
    }

    if(file_or_path != nullptr && !mountdir.empty()) {
        std::string p(file_or_path);
        if(p.rfind(mountdir, 0) == 0) {
            return true;
        }
    }

    if(file_actions != nullptr && !mountdir.empty()) {
        auto fa = reinterpret_cast<const struct gkfs_posix_spawn_file_actions*>(
                file_actions);
        if(fa->__actions != nullptr && fa->__used > 0) {
            for(int i = 0; i < fa->__used; ++i) {
                const auto& act = fa->__actions[i];
                if(act.tag == 3 /* spawn_do_chdir */) {
                    if(act.action.chdir_action.path != nullptr) {
                        std::string target(act.action.chdir_action.path);
                        if(target.rfind(mountdir, 0) == 0) {
                            return true;
                        }
                    }
                } else if(act.tag == 2 /* spawn_do_open */) {
                    if(act.action.open_action.path != nullptr) {
                        std::string target(act.action.open_action.path);
                        if(target.rfind(mountdir, 0) == 0) {
                            return true;
                        }
                    }
                }
            }
        }
    }

    return false;
}

int
gkfs_posix_spawn_impl(pid_t* __restrict pid,
                      const char* __restrict path_or_file, bool is_spawnp,
                      const posix_spawn_file_actions_t* __restrict file_actions,
                      const posix_spawnattr_t* __restrict attrp,
                      char* const argv[], char* const envp[]) {
    int errpipe[2];
    if(syscall_no_intercept(SYS_pipe2, errpipe, O_CLOEXEC) != 0) {
        return errno;
    }

    pid_t child_pid = fork();
    if(child_pid < 0) {
        int err = errno;
        syscall_no_intercept(SYS_close, errpipe[0]);
        syscall_no_intercept(SYS_close, errpipe[1]);
        return err;
    }

    if(child_pid > 0) {
        // Parent process
        syscall_no_intercept(SYS_close, errpipe[1]);
        int child_error = 0;
        long n = 0;
        do {
            n = syscall_no_intercept(SYS_read, errpipe[0], &child_error,
                                     sizeof(child_error));
        } while(n < 0 && syscall_error_code(n) == EINTR);
        syscall_no_intercept(SYS_close, errpipe[0]);

        if(n > 0) {
            int status = 0;
            waitpid(child_pid, &status, 0);
            return child_error;
        }

        if(pid != nullptr) {
            *pid = child_pid;
        }
        return 0;
    }

    // Child process: execute completely via direct syscalls without RPC
    syscall_no_intercept(SYS_close, errpipe[0]);

    auto fail_child = [&](int err) {
        syscall_no_intercept(SYS_write, errpipe[1], &err, sizeof(err));
        _exit(127);
    };

    if(attrp != nullptr) {
        short int flags = 0;
        posix_spawnattr_getflags(attrp, &flags);

        if(flags & POSIX_SPAWN_RESETIDS) {
            if(seteuid(getuid()) != 0 || setegid(getgid()) != 0) {
                fail_child(errno);
            }
        }

        if(flags & POSIX_SPAWN_SETPGROUP) {
            pid_t pgroup = 0;
            posix_spawnattr_getpgroup(attrp, &pgroup);
            if(setpgid(0, pgroup) != 0) {
                fail_child(errno);
            }
        }

#ifdef POSIX_SPAWN_SETSID
        if(flags & POSIX_SPAWN_SETSID) {
            if(setsid() == -1) {
                fail_child(errno);
            }
        }
#endif

        if(flags & POSIX_SPAWN_SETSIGMASK) {
            sigset_t mask;
            posix_spawnattr_getsigmask(attrp, &mask);
            sigprocmask(SIG_SETMASK, &mask, nullptr);
        }

        if(flags & POSIX_SPAWN_SETSIGDEF) {
            sigset_t defmask;
            posix_spawnattr_getsigdefault(attrp, &defmask);
            for(int sig = 1; sig < NSIG; ++sig) {
                if(sigismember(&defmask, sig)) {
                    signal(sig, SIG_DFL);
                }
            }
        }

        if(flags & POSIX_SPAWN_SETSCHEDPARAM) {
            struct sched_param param;
            posix_spawnattr_getschedparam(attrp, &param);
            sched_setparam(0, &param);
        }

        if(flags & POSIX_SPAWN_SETSCHEDULER) {
            int policy = 0;
            struct sched_param param;
            posix_spawnattr_getschedpolicy(attrp, &policy);
            posix_spawnattr_getschedparam(attrp, &param);
            sched_setscheduler(0, policy, &param);
        }
    }

    std::string effective_cwd = CTX ? CTX->cwd() : "";
    const std::string& mountdir = CTX ? CTX->mountdir() : "";

    if(file_actions != nullptr) {
        auto fa = reinterpret_cast<const struct gkfs_posix_spawn_file_actions*>(
                file_actions);
        if(fa->__actions != nullptr && fa->__used > 0) {
            for(int i = 0; i < fa->__used; ++i) {
                const auto& act = fa->__actions[i];
                switch(act.tag) {
                    case 0: /* spawn_do_close */ {
                        syscall_no_intercept(SYS_close,
                                             act.action.close_action.fd);
                        break;
                    }
                    case 1: /* spawn_do_dup2 */ {
                        long res = syscall_no_intercept(
                                SYS_dup2, act.action.dup2_action.fd,
                                act.action.dup2_action.newfd);
                        if(res < 0) {
                            fail_child(syscall_error_code(res));
                        }
                        break;
                    }
                    case 2: /* spawn_do_open */ {
                        long res = syscall_no_intercept(
                                SYS_openat, AT_FDCWD,
                                act.action.open_action.path,
                                act.action.open_action.oflag,
                                act.action.open_action.mode);
                        if(res < 0) {
                            fail_child(syscall_error_code(res));
                        }
                        int fd = static_cast<int>(res);
                        if(fd != act.action.open_action.fd) {
                            long dres = syscall_no_intercept(
                                    SYS_dup2, fd, act.action.open_action.fd);
                            syscall_no_intercept(SYS_close, fd);
                            if(dres < 0) {
                                fail_child(syscall_error_code(dres));
                            }
                        }
                        break;
                    }
                    case 3: /* spawn_do_chdir */ {
                        if(act.action.chdir_action.path != nullptr) {
                            std::string target = act.action.chdir_action.path;
                            if(!mountdir.empty() &&
                               target.rfind(mountdir, 0) == 0) {
                                effective_cwd = target;
                                long cres = syscall_no_intercept(
                                        SYS_chdir, mountdir.c_str());
                                if(cres < 0) {
                                    syscall_no_intercept(SYS_chdir, "/");
                                }
                            } else {
                                effective_cwd.clear();
                                long cres = syscall_no_intercept(
                                        SYS_chdir, target.c_str());
                                if(cres < 0) {
                                    fail_child(syscall_error_code(cres));
                                }
                            }
                        }
                        break;
                    }
                    case 4: /* spawn_do_fchdir */ {
                        long cres = syscall_no_intercept(
                                SYS_fchdir, act.action.fchdir_action.fd);
                        if(cres < 0) {
                            fail_child(syscall_error_code(cres));
                        }
                        break;
                    }
                    case 5: /* spawn_do_closefrom */ {
#ifdef SYS_close_range
                        syscall_no_intercept(SYS_close_range,
                                             act.action.closefrom_action.lowfd,
                                             ~0U, 0);
#else
                        for(int fd = act.action.closefrom_action.lowfd;
                            fd < 1024; ++fd) {
                            if(fd != errpipe[1])
                                syscall_no_intercept(SYS_close, fd);
                        }
#endif
                        break;
                    }
                    case 6: /* spawn_do_tcsetpgrp */ {
                        tcsetpgrp(act.action.tcsetpgrp_action.pgrp,
                                  act.action.tcsetpgrp_action.pgrp);
                        break;
                    }
                    default:
                        break;
                }
            }
        }
    }

    if(!effective_cwd.empty() && !mountdir.empty() &&
       effective_cwd.rfind(mountdir, 0) == 0) {
        long cres = syscall_no_intercept(SYS_chdir, mountdir.c_str());
        if(cres < 0) {
            syscall_no_intercept(SYS_chdir, "/");
        }
    }

    char* const* base_env = envp != nullptr ? envp : environ;
    std::vector<std::string> env_strings;
    std::vector<char*> new_env;

    bool has_gkfs_cwd = false;
    bool has_gkfs_mountdir = false;
    bool has_gkfs_hosts = false;
    bool has_preload = false;

    if(base_env != nullptr) {
        for(char* const* e = base_env; *e != nullptr; ++e) {
            std::string var(*e);
            if(var.rfind("LIBGKFS_CWD=", 0) == 0) {
                if(!effective_cwd.empty()) {
                    has_gkfs_cwd = true;
                    env_strings.push_back("LIBGKFS_CWD=" + effective_cwd);
                }
            } else {
                if(var.rfind("LIBGKFS_MOUNTDIR=", 0) == 0)
                    has_gkfs_mountdir = true;
                if(var.rfind("LIBGKFS_HOSTS_FILE=", 0) == 0)
                    has_gkfs_hosts = true;
                if(var.rfind("LD_PRELOAD=", 0) == 0)
                    has_preload = true;
                env_strings.push_back(var);
            }
        }
    }

    if(!has_gkfs_cwd && !effective_cwd.empty()) {
        env_strings.push_back("LIBGKFS_CWD=" + effective_cwd);
    }
    if(!has_gkfs_mountdir && !mountdir.empty()) {
        env_strings.push_back("LIBGKFS_MOUNTDIR=" + mountdir);
    }
    if(!has_gkfs_hosts && ::getenv("LIBGKFS_HOSTS_FILE") != nullptr) {
        env_strings.push_back(std::string("LIBGKFS_HOSTS_FILE=") +
                              ::getenv("LIBGKFS_HOSTS_FILE"));
    }
    if(!has_preload && ::getenv("LD_PRELOAD") != nullptr) {
        env_strings.push_back(std::string("LD_PRELOAD=") +
                              ::getenv("LD_PRELOAD"));
    }

    new_env.reserve(env_strings.size() + 1);
    for(auto& s : env_strings) {
        new_env.push_back(const_cast<char*>(s.c_str()));
    }
    new_env.push_back(nullptr);

    if(is_spawnp) {
        execvpe(path_or_file, argv, new_env.data());
    } else {
        execve(path_or_file, argv, new_env.data());
    }

    int err = errno;
    fail_child(err);
    return 127;
}

} // namespace

extern "C" {

int
posix_spawn(pid_t* __restrict pid, const char* __restrict path,
            const posix_spawn_file_actions_t* __restrict file_actions,
            const posix_spawnattr_t* __restrict attrp, char* const argv[],
            char* const envp[]) {
    if(posix_spawn_involves_gkfs(path, file_actions)) {
        LOG(DEBUG, "[GKFS] posix_spawn handled internally for path='{}'", path);
        return gkfs_posix_spawn_impl(pid, path, false, file_actions, attrp,
                                     argv, envp);
    }
    return dlsym_posix_spawn(pid, path, file_actions, attrp, argv, envp);
}

int
posix_spawnp(pid_t* __restrict pid, const char* __restrict file,
             const posix_spawn_file_actions_t* __restrict file_actions,
             const posix_spawnattr_t* __restrict attrp, char* const argv[],
             char* const envp[]) {
    if(posix_spawn_involves_gkfs(file, file_actions)) {
        LOG(DEBUG, "[GKFS] posix_spawnp handled internally for file='{}'",
            file);
        return gkfs_posix_spawn_impl(pid, file, true, file_actions, attrp, argv,
                                     envp);
    }
    return dlsym_posix_spawnp(pid, file, file_actions, attrp, argv, envp);
}

} // extern "C"