Program Listing for File hooks.cpp

Return to documentation for file (src/client/hooks.cpp)

/*
  Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain
  Copyright 2015-2024, 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 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.

  GekkoFS' POSIX interface 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
*/

#include <client/hooks.hpp>
#include <client/preload.hpp>
#include <client/preload_util.hpp>
#include <client/logging.hpp>
#include <client/gkfs_functions.hpp>
#include <client/path.hpp>
#include <client/open_dir.hpp>

#include <common/path_util.hpp>

#include <memory>

extern "C" {
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/statfs.h>
}

namespace {

// TODO replace all internal gkfs errno variable usage with LEAF
inline int
with_errno(int ret) {
    return (ret < 0) ? -errno : ret;
}

} // namespace

namespace gkfs::hook {

int
hook_openat(int dirfd, const char* cpath, int flags, mode_t mode) {

    LOG(DEBUG, "{}() called with fd: {}, path: \"{}\", flags: {}, mode: {}",
        __func__, dirfd, cpath, flags, mode);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_openat, dirfd, cpath, flags,
                                                mode);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_openat, dirfd,
                                                resolved.c_str(), flags, mode);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            return with_errno(gkfs::syscall::gkfs_open(resolved, mode, flags));

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

int
hook_close(int fd) {

    LOG(DEBUG, "{}() called with fd: {}", __func__, fd);

    if(CTX->file_map()->exist(fd)) {
        // No call to the daemon is required
        CTX->file_map()->remove(fd);
        return 0;
    }

    if(CTX->is_internal_fd(fd)) {
        // the client application (for some reason) is trying to close an
        // internal fd: ignore it
        return 0;
    }

    return syscall_no_intercept_wrapper(SYS_close, fd);
}
#ifdef SYS_stat
int
hook_stat(const char* path, struct stat* buf) {

    LOG(DEBUG, "{}() called with path: \"{}\", buf: {}", __func__, path,
        fmt::ptr(buf));

    std::string rel_path;
    if(CTX->relativize_path(path, rel_path, false)) {
        return with_errno(gkfs::syscall::gkfs_stat(rel_path, buf));
    }

    return syscall_no_intercept_wrapper(SYS_stat, rel_path.c_str(), buf);
}
#endif

#ifdef STATX_TYPE

int
hook_statx(int dirfd, const char* path, int flags, unsigned int mask,
           struct ::statx* buf) {

    LOG(DEBUG,
        "{}() called with dirfd: '{}', path: \"{}\", flags: '{}', mask: '{}', buf: '{}'",
        __func__, dirfd, path, flags, mask, fmt::ptr(buf));

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, path, resolved);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_statx, dirfd, path, flags,
                                                mask, buf);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(
                    SYS_statx, dirfd, resolved.c_str(), flags, mask, buf);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            return with_errno(gkfs::syscall::gkfs_statx(dirfd, resolved.c_str(),
                                                        flags, mask, buf));

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }

    return syscall_no_intercept(SYS_statx, dirfd, path, flags, mask, buf);
}

#endif

#ifdef SYS_lstat
int
hook_lstat(const char* path, struct stat* buf) {

    LOG(DEBUG, "{}() called with path: \"{}\", buf: {}", __func__, path,
        fmt::ptr(buf));

    std::string rel_path;
    if(CTX->relativize_path(path, rel_path)) {
        return with_errno(gkfs::syscall::gkfs_stat(rel_path, buf));
    }
    return syscall_no_intercept_wrapper(SYS_lstat, rel_path.c_str(), buf);
}
#endif

int
hook_fstat(unsigned int fd, struct stat* buf) {

    LOG(DEBUG, "{}() called with fd: {}, buf: {}", __func__, fd, fmt::ptr(buf));

    if(CTX->file_map()->exist(fd)) {
        auto path = CTX->file_map()->get(fd)->path();
#ifdef HAS_RENAME
        // Special case for fstat and rename, fd points to new file...
        // We can change file_map and recall
        auto md = gkfs::utils::get_metadata(path, false);
        if(md.has_value() && md.value().blocks() == -1) {
            path = md.value().rename_path();
        }
#endif
        return with_errno(gkfs::syscall::gkfs_stat(path, buf));
    }
    return syscall_no_intercept_wrapper(SYS_fstat, fd, buf);
}

int
hook_fstatat(int dirfd, const char* cpath, struct stat* buf, int flags) {

    LOG(DEBUG, "{}() called with path: \"{}\", fd: {}, buf: {}, flags: {}",
        __func__, cpath, dirfd, fmt::ptr(buf), flags);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, flags);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_newfstatat, dirfd, cpath,
                                                buf, flags);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_newfstatat, dirfd,
                                                resolved.c_str(), buf, flags);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            return with_errno(gkfs::syscall::gkfs_stat(resolved, buf));

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

int
hook_read(unsigned int fd, void* buf, size_t count) {

    LOG(DEBUG, "{}() called with fd: {}, buf: {} count: {}", __func__, fd,
        fmt::ptr(buf), count);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_read(fd, buf, count));
    }
    return syscall_no_intercept_wrapper(SYS_read, fd, buf, count);
}

int
hook_pread(unsigned int fd, char* buf, size_t count, loff_t pos) {

    LOG(DEBUG, "{}() called with fd: {}, buf: {}, count: {}, pos: {}", __func__,
        fd, fmt::ptr(buf), count, pos);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_pread_ws(fd, buf, count, pos));
    }
    /* Since kernel 2.6: pread() became pread64(), and pwrite() became
     * pwrite64(). */
    return syscall_no_intercept_wrapper(SYS_pread64, fd, buf, count, pos);
}

int
hook_readv(unsigned long fd, const struct iovec* iov, unsigned long iovcnt) {

    LOG(DEBUG, "{}() called with fd: {}, iov: {}, iovcnt: {}", __func__, fd,
        fmt::ptr(iov), iovcnt);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_readv(fd, iov, iovcnt));
    }
    return syscall_no_intercept_wrapper(SYS_readv, fd, iov, iovcnt);
}

int
hook_preadv(unsigned long fd, const struct iovec* iov, unsigned long iovcnt,
            unsigned long pos_l, unsigned long pos_h) {

    LOG(DEBUG,
        "{}() called with fd: {}, iov: {}, iovcnt: {}, "
        "pos_l: {},"
        "pos_h: {}",
        __func__, fd, fmt::ptr(iov), iovcnt, pos_l, pos_h);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_preadv(fd, iov, iovcnt, pos_l));
    }
    return syscall_no_intercept_wrapper(SYS_preadv, fd, iov, iovcnt, pos_l);
}

int
hook_write(unsigned int fd, const char* buf, size_t count) {

    LOG(DEBUG, "{}() called with fd: {}, buf: {}, count {}", __func__, fd,
        fmt::ptr(buf), count);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_write(fd, buf, count));
    }
    return syscall_no_intercept_wrapper(SYS_write, fd, buf, count);
}

int
hook_pwrite(unsigned int fd, const char* buf, size_t count, loff_t pos) {

    LOG(DEBUG, "{}() called with fd: {}, buf: {}, count: {}, pos: {}", __func__,
        fd, fmt::ptr(buf), count, pos);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_pwrite_ws(fd, buf, count, pos));
    }
    /* Since kernel 2.6: pread() became pread64(), and pwrite() became
     * pwrite64(). */
    return syscall_no_intercept_wrapper(SYS_pwrite64, fd, buf, count, pos);
}

int
hook_writev(unsigned long fd, const struct iovec* iov, unsigned long iovcnt) {

    LOG(DEBUG, "{}() called with fd: {}, iov: {}, iovcnt: {}", __func__, fd,
        fmt::ptr(iov), iovcnt);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_writev(fd, iov, iovcnt));
    }
    return syscall_no_intercept_wrapper(SYS_writev, fd, iov, iovcnt);
}

int
hook_pwritev(unsigned long fd, const struct iovec* iov, unsigned long iovcnt,
             unsigned long pos_l, unsigned long pos_h) {

    LOG(DEBUG,
        "{}() called with fd: {}, iov: {}, iovcnt: {}, "
        "pos_l: {},"
        "pos_h: {}",
        __func__, fd, fmt::ptr(iov), iovcnt, pos_l, pos_h);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_pwritev(fd, iov, iovcnt, pos_l));
    }
    return syscall_no_intercept_wrapper(SYS_pwritev, fd, iov, iovcnt, pos_l);
}

int
hook_unlinkat(int dirfd, const char* cpath, int flags) {

    LOG(DEBUG, "{}() called with dirfd: {}, path: \"{}\", flags: {}", __func__,
        dirfd, cpath, flags);

    if((flags & ~AT_REMOVEDIR) != 0) {
        LOG(ERROR, "{}() Flags unknown: {}", __func__, flags);
        return -EINVAL;
    }

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, false);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_unlinkat, dirfd, cpath,
                                                flags);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_unlinkat, dirfd,
                                                resolved.c_str(), flags);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            if(flags & AT_REMOVEDIR) {
                return with_errno(gkfs::syscall::gkfs_rmdir(resolved));
            } else {
                return with_errno(gkfs::syscall::gkfs_remove(resolved));
            }

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

int
hook_symlinkat(const char* oldname, int newdfd, const char* newname) {

    LOG(DEBUG, "{}() called with oldname: \"{}\", newfd: {}, newname: \"{}\"",
        __func__, oldname, newdfd, newname);

    std::string oldname_resolved;
    if(CTX->relativize_path(oldname, oldname_resolved)) {
        LOG(WARNING, "{}() operation not supported", __func__);
        return -ENOTSUP;
    }

    std::string newname_resolved;
    auto rstatus =
            CTX->relativize_fd_path(newdfd, newname, newname_resolved, false);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_symlinkat, oldname, newdfd,
                                                newname);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_symlinkat, oldname, newdfd,
                                                newname_resolved.c_str());

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            LOG(WARNING, "{}() operation not supported", __func__);
            return -ENOTSUP;

        default:
            LOG(ERROR, "{}() relativize status unknown", __func__);
            return -EINVAL;
    }
}

int
hook_flock(unsigned long fd, int flags) {
    LOG(ERROR, "{}() called flock (Not Supported) with fd '{}' flags '{}'",
        __func__, fd, flags);

    if(CTX->file_map()->exist(fd)) {
        return 0;
    } else
        return -EBADF;
}

#ifdef SYS_access
int
hook_access(const char* path, int mask) {

    LOG(DEBUG, "{}() called path: \"{}\", mask: {}", __func__, path, mask);

    std::string rel_path;
    if(CTX->relativize_path(path, rel_path)) {
        auto ret = gkfs::syscall::gkfs_access(rel_path, mask);
        if(ret < 0) {
            return -errno;
        }
        return ret;
    }
    return syscall_no_intercept_wrapper(SYS_access, rel_path.c_str(), mask);
}
#endif

int
hook_faccessat(int dirfd, const char* cpath, int mode) {

    LOG(DEBUG, "{}() called with dirfd: {}, path: \"{}\", mode: {}", __func__,
        dirfd, cpath, mode);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_faccessat, dirfd, cpath,
                                                mode);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_faccessat, dirfd,
                                                resolved.c_str(), mode);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            return with_errno(gkfs::syscall::gkfs_access(resolved, mode));

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

#ifdef SYS_faccessat2
int
hook_faccessat2(int dirfd, const char* cpath, int mode, int flags) {

    LOG(DEBUG,
        "{}() called with dirfd: '{}', path: '{}', mode: '{}', flags: '{}'",
        __func__, dirfd, cpath, mode, flags);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_faccessat2, dirfd, cpath,
                                                mode, flags);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_faccessat2, dirfd,
                                                resolved.c_str(), mode, flags);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            // we do not use permissions and therefore do not handle `flags` for
            // now
            return with_errno(gkfs::syscall::gkfs_access(resolved, mode));

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}
#endif

off_t
hook_lseek(unsigned int fd, off_t offset, unsigned int whence) {

    LOG(DEBUG, "{}() called with fd: {}, offset: {}, whence: {}", __func__, fd,
        offset, whence);

    if(CTX->file_map()->exist(fd)) {
        auto off_ret = gkfs::syscall::gkfs_lseek(
                fd, static_cast<off64_t>(offset), whence);
        if(off_ret > std::numeric_limits<off_t>::max()) {
            return -EOVERFLOW;
        } else if(off_ret < 0) {
            return -errno;
        }
        LOG(DEBUG, "{}() returning {}", __func__, off_ret);
        return off_ret;
    }
    return syscall_no_intercept_wrapper(SYS_lseek, fd, offset, whence);
}

int
hook_truncate(const char* path, long length) {

    LOG(DEBUG, "{}() called with path: {}, offset: {}", __func__, path, length);

    std::string rel_path;
    if(CTX->relativize_path(path, rel_path)) {
        return with_errno(gkfs::syscall::gkfs_truncate(rel_path, length));
    }
    return syscall_no_intercept_wrapper(SYS_truncate, rel_path.c_str(), length);
}

int
hook_ftruncate(unsigned int fd, unsigned long length) {

    LOG(DEBUG, "{}() called with fd: {}, offset: {}", __func__, fd, length);

    if(CTX->file_map()->exist(fd)) {
        auto path = CTX->file_map()->get(fd)->path();
        return with_errno(gkfs::syscall::gkfs_truncate(path, length));
    }
    return syscall_no_intercept_wrapper(SYS_ftruncate, fd, length);
}

int
hook_dup(unsigned int fd) {

    LOG(DEBUG, "{}() called with oldfd: {}", __func__, fd);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_dup(fd));
    }
    return syscall_no_intercept_wrapper(SYS_dup, fd);
}
#ifdef SYS_dup2
int
hook_dup2(unsigned int oldfd, unsigned int newfd) {

    LOG(DEBUG, "{}() called with oldfd: {}, newfd: {}", __func__, oldfd, newfd);

    if(CTX->file_map()->exist(oldfd)) {
        return with_errno(gkfs::syscall::gkfs_dup2(oldfd, newfd));
    }
    return syscall_no_intercept_wrapper(SYS_dup2, oldfd, newfd);
}
#endif
int
hook_dup3(unsigned int oldfd, unsigned int newfd, int flags) {

    LOG(DEBUG, "{}() called with oldfd: {}, newfd: {}, flags: {}", __func__,
        oldfd, newfd, flags);

    if(CTX->file_map()->exist(oldfd)) {
        // TODO implement O_CLOEXEC flag first which is used with fcntl(2)
        // It is in glibc since kernel 2.9. So maybe not that important :)
        LOG(WARNING, "{}() Not supported", __func__);
        return -ENOTSUP;
    }
    return syscall_no_intercept_wrapper(SYS_dup3, oldfd, newfd, flags);
}
#ifdef SYS_getdents
int
hook_getdents(unsigned int fd, struct linux_dirent* dirp, unsigned int count) {

    LOG(DEBUG, "{}() called with fd: {}, dirp: {}, count: {}", __func__, fd,
        fmt::ptr(dirp), count);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_getdents(fd, dirp, count));
    }
    return syscall_no_intercept_wrapper(SYS_getdents, fd, dirp, count);
}
#endif

int
hook_getdents64(unsigned int fd, struct linux_dirent64* dirp,
                unsigned int count) {

    LOG(DEBUG, "{}() called with fd: {}, dirp: {}, count: {}", __func__, fd,
        fmt::ptr(dirp), count);

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_getdents64(fd, dirp, count));
    }
    return syscall_no_intercept_wrapper(SYS_getdents64, fd, dirp, count);
}


int
hook_mkdirat(int dirfd, const char* cpath, mode_t mode) {

    LOG(DEBUG, "{}() called with dirfd: {}, path: \"{}\", mode: {}", __func__,
        dirfd, cpath, mode);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_mkdirat, dirfd,
                                                resolved.c_str(), mode);

        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_mkdirat, dirfd, cpath,
                                                mode);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            return with_errno(
                    gkfs::syscall::gkfs_create(resolved, mode | S_IFDIR));

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

int
hook_fchmodat(int dirfd, const char* cpath, mode_t mode) {

    LOG(DEBUG, "{}() called dirfd: {}, path: \"{}\", mode: {}", __func__, dirfd,
        cpath, mode);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_fchmodat, dirfd, cpath,
                                                mode);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_fchmodat, dirfd,
                                                resolved.c_str(), mode);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            LOG(WARNING, "{}() operation not supported", __func__);
            return -ENOTSUP;

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

int
hook_fchmod(unsigned int fd, mode_t mode) {

    LOG(DEBUG, "{}() called with fd: {}, mode: {}", __func__, fd, mode);

    if(CTX->file_map()->exist(fd)) {
        LOG(WARNING, "{}() operation not supported", __func__);
        return -ENOTSUP;
    }
    return syscall_no_intercept_wrapper(SYS_fchmod, fd, mode);
}

int
hook_chdir(const char* path) {

    LOG(DEBUG, "{}() called with path: \"{}\"", __func__, path);

    std::string rel_path;
    bool internal = CTX->relativize_path(path, rel_path);
    if(internal) {
        // path falls in our namespace
        auto md = gkfs::utils::get_metadata(rel_path);
        if(!md) {
            LOG(ERROR, "{}() path {} errno {}", __func__, path, errno);
            return -errno;
        }

        if(!S_ISDIR(md->mode())) {
            LOG(ERROR, "{}() path is not a directory", __func__);
            return -ENOTDIR;
        }
        // TODO get complete path from relativize_path instead of
        // removing mountdir and then adding again here
        rel_path.insert(0, CTX->mountdir());
        if(gkfs::path::has_trailing_slash(rel_path)) {
            // open_dir is '/'
            rel_path.pop_back();
        }
    }
    try {
        gkfs::path::set_cwd(rel_path, internal);
    } catch(const std::system_error& se) {
        return -(se.code().value());
    }
    return 0;
}

int
hook_fchdir(unsigned int fd) {

    LOG(DEBUG, "{}() called with fd: {}", __func__, fd);

    if(CTX->file_map()->exist(fd)) {
        auto open_dir = CTX->file_map()->get_dir(fd);
        if(open_dir == nullptr) {
            // Cast did not succeeded: open_file is a regular file
            LOG(ERROR, "{}() file descriptor refers to a normal file",
                __func__);
            return -EBADF;
        }

        std::string new_path = CTX->mountdir() + open_dir->path();
        if(gkfs::path::has_trailing_slash(new_path)) {
            // open_dir is '/'
            new_path.pop_back();
        }
        try {
            gkfs::path::set_cwd(new_path, true);
        } catch(const std::system_error& se) {
            return -(se.code().value());
        }
    } else {
        long ret = syscall_no_intercept_wrapper(SYS_fchdir, fd);
        if(ret < 0) {
            throw std::system_error(
                    syscall_error_code(ret), std::system_category(),
                    "Failed to change directory (fchdir syscall)");
        }
        gkfs::path::unset_env_cwd();
        CTX->cwd(gkfs::path::get_sys_cwd());
    }
    return 0;
}

int
hook_getcwd(char* buf, unsigned long size) {

    LOG(DEBUG, "{}() called with buf: {}, size: {}", __func__, fmt::ptr(buf),
        size);

    if(CTX->cwd().size() + 1 > size) {
        LOG(ERROR, "{}() buffer too small to host current working dir",
            __func__);
        return -ERANGE;
    }

    strcpy(buf, CTX->cwd().c_str());
    return (CTX->cwd().size() + 1);
}

int
hook_readlinkat(int dirfd, const char* cpath, char* buf, int bufsiz) {

    LOG(DEBUG, "{}() called with dirfd: {}, path \"{}\", buf: {}, bufsize: {}",
        __func__, dirfd, cpath, fmt::ptr(buf), bufsiz);

    std::string resolved;
    auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, false);
    switch(rstatus) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            return syscall_no_intercept_wrapper(SYS_readlinkat, dirfd, cpath,
                                                buf, bufsiz);

        case gkfs::preload::RelativizeStatus::external:
            return syscall_no_intercept_wrapper(SYS_readlinkat, dirfd,
                                                resolved.c_str(), buf, bufsiz);

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            LOG(WARNING, "{}() not supported", __func__);
            return -ENOTSUP;

        default:
            LOG(ERROR, "{}() relativize status unknown: {}", __func__);
            return -EINVAL;
    }
}

int
hook_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) {

    LOG(DEBUG, "{}() called with fd: {}, cmd: {}, arg: {}", __func__, fd, cmd,
        arg);

    if(!CTX->file_map()->exist(fd)) {
        return syscall_no_intercept_wrapper(SYS_fcntl, fd, cmd, arg);
    }
    int ret;
    switch(cmd) {

        case F_DUPFD:
            LOG(DEBUG, "{}() F_DUPFD on fd {}", __func__, fd);
            return with_errno(gkfs::syscall::gkfs_dup(fd));

        case F_DUPFD_CLOEXEC:
            LOG(DEBUG, "{}() F_DUPFD_CLOEXEC on fd {}", __func__, fd);
            ret = gkfs::syscall::gkfs_dup(fd);
            if(ret == -1) {
                return -errno;
            }
            CTX->file_map()->get(fd)->set_flag(
                    gkfs::filemap::OpenFile_flags::cloexec, true);
            return ret;

        case F_GETFD:
            LOG(DEBUG, "{}() F_GETFD on fd {}", __func__, fd);
            if(CTX->file_map()->get(fd)->get_flag(
                       gkfs::filemap::OpenFile_flags::cloexec)) {
                return FD_CLOEXEC;
            }
            return 0;

        case F_GETFL:
            LOG(DEBUG, "{}() F_GETFL on fd {}", __func__, fd);
            ret = 0;
            if(CTX->file_map()->get(fd)->get_flag(
                       gkfs::filemap::OpenFile_flags::rdonly)) {
                ret |= O_RDONLY;
            }
            if(CTX->file_map()->get(fd)->get_flag(
                       gkfs::filemap::OpenFile_flags::wronly)) {
                ret |= O_WRONLY;
            }
            if(CTX->file_map()->get(fd)->get_flag(
                       gkfs::filemap::OpenFile_flags::rdwr)) {
                ret |= O_RDWR;
            }
            return ret;

        case F_SETFD:
            LOG(DEBUG, "{}() [fd: {}, cmd: F_SETFD, FD_CLOEXEC: {}]", __func__,
                fd, (arg & FD_CLOEXEC));
            CTX->file_map()->get(fd)->set_flag(
                    gkfs::filemap::OpenFile_flags::cloexec, (arg & FD_CLOEXEC));
            return 0;

        case F_GETLK:
            LOG(ERROR, "{}() F_GETLK on fd (Not Supported) {}", __func__, fd);
            return 0;

        case F_SETLK:
            LOG(ERROR, "{}() F_SETLK on fd (Not Supported) {}", __func__, fd);
            return 0;

        default:
            LOG(ERROR, "{}() unrecognized command {} on fd {}", __func__, cmd,
                fd);
            return -ENOTSUP;
    }
}

int
hook_renameat(int olddfd, const char* oldname, int newdfd, const char* newname,
              unsigned int flags) {

    LOG(DEBUG,
        "{}() called with olddfd: {}, oldname: \"{}\", newfd: {}, "
        "newname \"{}\", flags {}",
        __func__, olddfd, oldname, newdfd, newname, flags);

    const char* oldpath_pass;
    std::string oldpath_resolved;
    auto oldpath_status =
            CTX->relativize_fd_path(olddfd, oldname, oldpath_resolved);
    switch(oldpath_status) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            oldpath_pass = oldname;
            break;

        case gkfs::preload::RelativizeStatus::external:
            oldpath_pass = oldpath_resolved.c_str();
            break;

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
            break;

        default:
            LOG(ERROR, "{}() relativize status unknown", __func__);
            return -EINVAL;
    }

    const char* newpath_pass;
    std::string newpath_resolved;
    auto newpath_status =
            CTX->relativize_fd_path(newdfd, newname, newpath_resolved);
    switch(newpath_status) {
        case gkfs::preload::RelativizeStatus::fd_unknown:
            newpath_pass = newname;
            break;

        case gkfs::preload::RelativizeStatus::external:
            newpath_pass = newpath_resolved.c_str();
            break;

        case gkfs::preload::RelativizeStatus::fd_not_a_dir:
            return -ENOTDIR;

        case gkfs::preload::RelativizeStatus::internal:
#ifdef HAS_RENAME
            if(oldpath_status == gkfs::preload::RelativizeStatus::internal) {
                return with_errno(gkfs::syscall::gkfs_rename(oldpath_resolved,
                                                             newpath_resolved));
            } else {
                return -ENOTSUP;
            }
#else
            return -ENOTSUP;
#endif
        default:
            LOG(ERROR, "{}() relativize status unknown", __func__);
            return -EINVAL;
    }

    return syscall_no_intercept_wrapper(SYS_renameat2, olddfd, oldpath_pass,
                                        newdfd, newpath_pass, flags);
}

int
hook_statfs(const char* path, struct statfs* buf) {

    LOG(DEBUG, "{}() called with path: \"{}\", buf: {}", __func__, path,
        fmt::ptr(buf));

    std::string rel_path;
    if(CTX->relativize_path(path, rel_path)) {
        return with_errno(gkfs::syscall::gkfs_statfs(buf));
    }
    return syscall_no_intercept_wrapper(SYS_statfs, rel_path.c_str(), buf);
}

int
hook_fstatfs(unsigned int fd, struct statfs* buf) {

    LOG(DEBUG, "{}() called with fd: {}, buf: {}", __func__, fd, fmt::ptr(buf));

    if(CTX->file_map()->exist(fd)) {
        return with_errno(gkfs::syscall::gkfs_statfs(buf));
    }
    return syscall_no_intercept_wrapper(SYS_fstatfs, fd, buf);
}

/* The function should broadcast a flush message (pmem_persist i.e.) if the
 * application needs the capabilities*/
int
hook_fsync(unsigned int fd) {

    LOG(DEBUG, "{}() called with fd: {}", __func__, fd);

    if(CTX->file_map()->exist(fd)) {
        errno = 0;
        return 0;
    }

    return syscall_no_intercept_wrapper(SYS_fsync, fd);
}

int
hook_getxattr(const char* path, const char* name, void* value, size_t size) {

    LOG(DEBUG, "{}() called with path '{}' name '{}' value '{}' size '{}'",
        __func__, path, name, fmt::ptr(value), size);

    std::string rel_path;
    if(CTX->relativize_path(path, rel_path)) {
        return -ENOTSUP;
    }
    return syscall_no_intercept_wrapper(SYS_getxattr, path, name, value, size);
}

} // namespace gkfs::hook