Skip to content
/*
Copyright 2018-2025, Barcelona Supercomputing Center (BSC), Spain
Copyright 2015-2025, 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.
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 <iostream>
#include <dlfcn.h>
#include <cstdio>
#include <cstdarg>
#include <client/gkfs_libc.hpp>
#include <client/user_functions.hpp>
#include <client/hooks.hpp>
#include <atomic>
#include <client/preload_context.hpp>
#include <client/preload.hpp>
#include <client/open_file_map.hpp>
#include <common/path_util.hpp>
#include <client/open_dir.hpp>
#include <linux/const.h>
#include <client/logging.hpp>
#include <sstream>
#include <aio.h>
#include <signal.h>
std::atomic<bool> activated{false};
std::atomic<bool> initializing{false};
/**
* @brief Notes: we should return -1, and set up errno correctly on libc
* interception Actually it is done fine on nearly all operations, but we should
* take care
*/
// Define a debug macro, can be easily disabled
#define GKFS_TRACE
#ifdef GKFS_DEBUG_BUILD
#ifdef GKFS_TRACE
#define DEBUG_INFO(...) \
do { \
LOG(DEBUG, __VA_ARGS__); \
} while(0)
#else
#define DEBUG_INFO(...) /* No debug output */
#endif
#else
#define DEBUG_INFO(...)
#endif
// set to store void * addr, fd, length and offset
std::set<std::tuple<void*, int, size_t, off_t>> mmap_set;
void
initializeGekko() {
// if the activated is false call init
// Create a global mutex
if(!activated and !initializing) {
/* DEBUG_INFO("%d [BYPASS] >> DEACTIVATED GEKKOFS.... \n",
getpid()); initializing = true; init_libc(); activated = true;
initializing = false;
DEBUG_INFO("%d [BYPASS] >> ACTIVATED GEKKOFS.... \n", getpid());
*/
}
}
#define STRIP_PARENS(...) __VA_ARGS__
void
log_arguments(const char* symbol) {
DEBUG_INFO("[BYPASS] {}", symbol);
}
// Variadic case: 1+ arguments
template <typename... Args>
void
log_arguments(const char* symbol, Args&&... args) {
std::stringstream ss;
ss << "[BYPASS] Calling " << symbol << " with arguments: ";
((ss << "[" << typeid(Args).name() << "] " << args << " "), ...);
DEBUG_INFO("{}", ss.str());
}
// Variadic case: 1+ arguments
template <typename... Args>
void
log_argumentsx(const char* symbol, Args&&... args) {
std::stringstream ss;
ss << "[BYPASS-ERROR] Calling " << symbol << " with arguments: ";
((ss << "[" << typeid(Args).name() << "] " << args << " "), ...);
DEBUG_INFO("{}", ss.str());
}
/**
* @brief Macro to declare and implement a wrapper for a standard library
* function using dlsym.
*
* This macro simplifies the process of creating wrappers for functions we want
* to intercept. It declares a static function pointer to the real function and
* defines a wrapper function that:
* 1. Lazily loads the real function address using dlsym on the first call.
* 2. Handles dlsym errors by printing to stderr and returning an error value.
* 3. Calls the real function if loaded successfully.
*
* @param return_type The return type of the function.
* @param func_name The name of the function (used for wrapper and real function
* pointer naming).
* @param params The parameter list of the function (e.g., `(int fd, const char*
* path)`).
* @param args The argument list to pass to the real function (e.g., `(fd,
* path)`).
* @param symbol_name The symbol name to look up with dlsym (usually the same as
* `func_name`).
*/
#define DLSYM_WRAPPER(return_type, func_name, params, args, symbol_name) \
static return_type(*real_##func_name) params = nullptr; \
return_type dlsym_##func_name params { \
if(!real_##func_name) { \
real_##func_name = reinterpret_cast<return_type(*) params>( \
dlsym(RTLD_NEXT, symbol_name)); \
if(!real_##func_name) { \
fprintf(stderr, "dlsym failed for %s: %s\n", symbol_name, \
dlerror()); \
errno = ENOSYS; /* Set errno to ENOSYS to indicate function \
not supported */ \
return (return_type) - 1; /* Return error value */ \
} \
} \
log_arguments(symbol_name, STRIP_PARENS args); \
return real_##func_name args; \
}
#define NOLOGDLSYM_WRAPPER(return_type, func_name, params, args, symbol_name) \
static return_type(*real_##func_name) params = nullptr; \
return_type dlsym_##func_name params { \
if(!real_##func_name) { \
real_##func_name = reinterpret_cast<return_type(*) params>( \
dlsym(RTLD_NEXT, symbol_name)); \
if(!real_##func_name) { \
fprintf(stderr, "dlsym failed for %s: %s\n", symbol_name, \
dlerror()); \
errno = ENOSYS; /* Set errno to ENOSYS to indicate function \
not supported */ \
return (return_type) - 1; /* Return error value */ \
} \
} \
return real_##func_name args; \
}
#define CDLSYM_WRAPPER(return_type, func_name, params, args, symbol_name) \
static return_type(*real_##func_name) params = nullptr; \
return_type dlsym_##func_name params { \
if(!real_##func_name) { \
real_##func_name = reinterpret_cast<return_type(*) params>( \
dlsym(RTLD_NEXT, symbol_name)); \
if(!real_##func_name) { \
fprintf(stderr, "dlsym failed for %s: %s\n", symbol_name, \
dlerror()); \
errno = ENOSYS; /* Set errno to ENOSYS to indicate function \
not supported */ \
return (return_type) - 1; /* Return error value */ \
} \
} \
log_argumentsx(symbol_name, STRIP_PARENS args); \
return real_##func_name args; \
} \
return_type func_name params { \
return dlsym_##func_name args; \
}
// DLSYM_WRAPPER(int, open, (char* path, int flags, mode_t mode), (path, flags,
// mode), "open")
DLSYM_WRAPPER(int, open2, (const char* path, int flags, mode_t mode),
(path, flags, mode), "open")
DLSYM_WRAPPER(int, openat, (int fd, const char* path, int flags, mode_t mode),
(fd, path, flags, mode), "openat")
DLSYM_WRAPPER(int, open64, (const char* path, int flags, mode_t mode),
(path, flags, mode), "open64")
DLSYM_WRAPPER(int, close, (int fd), (fd), "close")
DLSYM_WRAPPER(int, close_range,
(unsigned int low, unsigned int high, int flags),
(low, high, flags), "close_range")
DLSYM_WRAPPER(int, creat, (const char* path, mode_t mode), (path, mode),
"creat")
DLSYM_WRAPPER(int, ftruncate, (int fd, off_t length), (fd, length), "ftruncate")
DLSYM_WRAPPER(ssize_t, read, (int fd, void* buf, size_t nbyte),
(fd, buf, nbyte), "read")
DLSYM_WRAPPER(ssize_t, write, (int fd, const void* buf, size_t nbyte),
(fd, buf, nbyte), "write")
DLSYM_WRAPPER(ssize_t, readv, (int fd, const struct iovec* iov, int iovcnt),
(fd, iov, iovcnt), "readv")
DLSYM_WRAPPER(ssize_t, writev, (int fd, const struct iovec* iov, int iovcnt),
(fd, iov, iovcnt), "writev")
DLSYM_WRAPPER(ssize_t, preadv,
(int fd, const struct iovec* iov, int iovcnt, off_t offset),
(fd, iov, iovcnt, offset), "preadv")
DLSYM_WRAPPER(ssize_t, pwritev,
(int fd, const struct iovec* iov, int iovcnt, off_t offset),
(fd, iov, iovcnt, offset), "pwritev")
DLSYM_WRAPPER(ssize_t, preadv2,
(int fd, const struct iovec* iov, int iovcnt, off_t offset,
int flags),
(fd, iov, iovcnt, offset, flags), "preadv2")
DLSYM_WRAPPER(ssize_t, pwritev2,
(int fd, const struct iovec* iov, int iovcnt, off_t offset,
int flags),
(fd, iov, iovcnt, offset, flags), "pwritev2")
DLSYM_WRAPPER(int, mkdir, (const char* path, mode_t mode), (path, mode),
"mkdir")
DLSYM_WRAPPER(int, mkdirat, (int dfd, const char* path, mode_t mode),
(dfd, path, mode), "mkdirat")
DLSYM_WRAPPER(int, rmdir, (const char* path), (path), "rmdir")
DLSYM_WRAPPER(ssize_t, pread, (int fd, void* buf, size_t count, off_t offset),
(fd, buf, count, offset), "pread")
DLSYM_WRAPPER(ssize_t, pwrite,
(int fd, const void* buf, size_t count, off_t offset),
(fd, buf, count, offset), "pwrite")
DLSYM_WRAPPER(ssize_t, pread64, (int fd, void* buf, size_t count, off_t offset),
(fd, buf, count, offset), "pread64")
DLSYM_WRAPPER(ssize_t, pwrite64,
(int fd, const void* buf, size_t count, off_t offset),
(fd, buf, count, offset), "pwrite64")
DLSYM_WRAPPER(int, mkstemp, (char* templates), (templates), "mkstemp")
DLSYM_WRAPPER(off_t, lseek, (int fd, off_t offset, int whence),
(fd, offset, whence), "lseek")
DLSYM_WRAPPER(off64_t, lseek64, (int fd, off64_t offset, int whence),
(fd, offset, whence), "lseek64")
DLSYM_WRAPPER(int, lxstat64, (int ver, const char* path, struct stat64* buf),
(ver, path, buf), "__lxstat64")
DLSYM_WRAPPER(int, xstat64, (int ver, const char* path, struct stat64* buf),
(ver, path, buf), "__xstat64")
DLSYM_WRAPPER(int, fxstat64, (int ver, int fd, struct stat64* buf),
(ver, fd, buf), "__fxstat64")
DLSYM_WRAPPER(int, __lxstat, (int ver, const char* path, struct stat* buf),
(ver, path, buf), "__lxstat")
DLSYM_WRAPPER(int, lstat, (const char* path, struct stat* buf), (path, buf),
"lstat")
DLSYM_WRAPPER(int, lstat64, (const char* path, struct stat64* buf), (path, buf),
"lstat64")
DLSYM_WRAPPER(int, stat, (int ver, const char* path, struct stat* buf),
(ver, path, buf), "__xstat")
DLSYM_WRAPPER(int, __fxstat, (int ver, int fd, struct stat* buf),
(ver, fd, buf), "__fxstat")
DLSYM_WRAPPER(int, fstat, (int fd, struct stat* buf), (fd, buf), "fstat")
DLSYM_WRAPPER(int, fstat64, (int fd, struct stat64* buf), (fd, buf), "fstat64")
DLSYM_WRAPPER(int, fxstatat,
(int ver, int dfd, const char* path, struct stat* buf, int flags),
(ver, dfd, path, buf, flags), "__fxstatat")
DLSYM_WRAPPER(int, fstatat,
(int dfd, const char* path, struct stat* buf, int flags),
(dfd, path, buf, flags), "fstatat")
DLSYM_WRAPPER(int, fstatat64,
(int dfd, const char* path, struct stat64* buf, int flags),
(dfd, path, buf, flags), "fstatat64")
DLSYM_WRAPPER(int, statx,
(int dirfd, const char* path, int flags, unsigned int mask,
struct statx* buf),
(dirfd, path, flags, mask, buf), "statx")
DLSYM_WRAPPER(int, rename, (const char* oldpath, const char* newpath),
(oldpath, newpath), "rename")
DLSYM_WRAPPER(int, renameat,
(int olddirfd, const char* oldpath, int newdirfd,
const char* newpath),
(olddirfd, oldpath, newdirfd, newpath), "renameat")
DLSYM_WRAPPER(int, renameat2,
(int olddirfd, const char* oldpath, int newdirfd,
const char* newpath, unsigned int flags),
(olddirfd, oldpath, newdirfd, newpath, flags), "renameat2")
DLSYM_WRAPPER(int, remove, (const char* path), (path), "remove")
DLSYM_WRAPPER(int, unlink, (const char* path), (path), "unlink")
DLSYM_WRAPPER(DIR*, opendir, (const char* dirname), (dirname), "opendir")
DLSYM_WRAPPER(DIR*, opendir64, (const char* dirname), (dirname), "opendir64")
DLSYM_WRAPPER(struct dirent*, readdir, (DIR * dirp), (dirp), "readdir")
DLSYM_WRAPPER(struct dirent64*, readdir64, (DIR * dirp), (dirp), "readdir64")
DLSYM_WRAPPER(int, closedir, (DIR * dirp), (dirp), "closedir")
DLSYM_WRAPPER(void, seekdir, (DIR * dirp, long loc), (dirp, loc), "seekdir")
DLSYM_WRAPPER(long, telldir, (DIR * dirp), (dirp), "telldir")
/*
static pid_t (*real_fork)(void) = NULL;
static pid_t (*real_vfork)(void) = NULL;
static pid_t (*real_clone)(int (*fn)(void*), void*, int, void*, ...) = NULL;
pid_t
vfork(void) {
if(!real_vfork)
real_vfork =
reinterpret_cast<int (*)(void)>(dlsym(((void*) -1l), "vfork"));
destroy_libc();
pid_t pid = real_vfork();
// vfork child shares memory until execve, so:
if(pid == 0) {
init_libc(); // Dangerous! Must be async-signal-safe
} else {
init_libc();
}
return pid;
}
pid_t
clone(int (*fn)(void*), void* stack, int flags, void* arg, ...) {
if(!real_clone)
real_clone =
reinterpret_cast<decltype(&::clone)>(dlsym(RTLD_NEXT, "clone"));
// Only handle process-creation clones
if(flags & (SIGCHLD | CLONE_VFORK)) {
destroy_libc();
}
pid_t pid = real_clone(fn, stack, flags, arg);
if(flags & (SIGCHLD | CLONE_VFORK)) {
(pid == 0) ? init_libc() : init_libc();
}
return pid;
}
*/
DLSYM_WRAPPER(int, pipe, (int pipefd[2]), (pipefd), "pipe")
DLSYM_WRAPPER(int, dup, (int fd), (fd), "dup")
DLSYM_WRAPPER(int, dup2, (int fd, int fd2), (fd, fd2), "dup2")
DLSYM_WRAPPER(int, dup3, (int fd, int fd2, int flags), (fd, fd2, flags), "dup3")
DLSYM_WRAPPER(void, exit, (int status), (status), "exit")
DLSYM_WRAPPER(int, chdir, (char* path), (path), "chdir")
DLSYM_WRAPPER(int, fchdir, (int fd), (fd), "fchdir")
DLSYM_WRAPPER(int, chmod, (char* path, mode_t mode), (path, mode), "chmod")
DLSYM_WRAPPER(int, fchmod, (int fd, mode_t mode), (fd, mode), "fchmod")
DLSYM_WRAPPER(int, chown, (char* path, uid_t owner, gid_t group),
(path, owner, group), "chown")
static int (*real_fcntl)(int fd, int cmd, ...) = nullptr;
DLSYM_WRAPPER(int, access, (const char* path, int mode), (path, mode), "access")
DLSYM_WRAPPER(int, faccessat, (int dfd, const char* path, int mode, int flags),
(dfd, path, mode, flags), "faccessat")
DLSYM_WRAPPER(char*, realpath, (const char* path, char* resolved_path),
(path, resolved_path), "realpath")
DLSYM_WRAPPER(int, fsync, (int fd), (fd), "fsync")
DLSYM_WRAPPER(int, flock, (int fd, int operation), (fd, operation), "flock")
DLSYM_WRAPPER(void*, mmap,
(void* addr, size_t length, int prot, int flags, int fd,
off_t offset),
(addr, length, prot, flags, fd, offset), "mmap")
DLSYM_WRAPPER(int, msync, (void* addr, size_t length, int flags),
(addr, length, flags), "msync")
DLSYM_WRAPPER(int, munmap, (void* addr, size_t length), (addr, length),
"munmap")
DLSYM_WRAPPER(FILE*, fopen, (const char* filename, const char* mode),
(filename, mode), "fopen")
DLSYM_WRAPPER(FILE*, fdopen, (int fd, const char* mode), (fd, mode), "fdopen")
DLSYM_WRAPPER(FILE*, freopen64,
(const char* filename, const char* mode, FILE* stream),
(filename, mode, stream), "freopen")
DLSYM_WRAPPER(int, fclose, (FILE * stream), (stream), "fclose")
DLSYM_WRAPPER(size_t, fread,
(void* ptr, size_t size, size_t nmemb, FILE* stream),
(ptr, size, nmemb, stream), "fread")
DLSYM_WRAPPER(size_t, fwrite,
(const void* ptr, size_t size, size_t nmemb, FILE* stream),
(ptr, size, nmemb, stream), "fwrite")
DLSYM_WRAPPER(int, fseek, (FILE * stream, long int offset, int whence),
(stream, offset, whence), "fseek")
DLSYM_WRAPPER(long, ftell, (FILE * stream), (stream), "ftell")
DLSYM_WRAPPER(void, rewind, (FILE * stream), (stream), "rewind")
DLSYM_WRAPPER(int, feof, (FILE * stream), (stream), "feof")
DLSYM_WRAPPER(void, clearerr, (FILE * stream), (stream), "clearerr")
DLSYM_WRAPPER(int, fputs, (const char* str, FILE* stream), (str, stream),
"fputs")
NOLOGDLSYM_WRAPPER(char*, fgets, (char* str, int n, FILE* stream),
(str, n, stream), "fgets")
NOLOGDLSYM_WRAPPER(int, fflush, (FILE * stream), (stream), "fflush")
DLSYM_WRAPPER(int, fchown, (int fd, uid_t owner, gid_t group),
(fd, owner, group), "fchown")
DLSYM_WRAPPER(int, futimes, (int fd, const struct timeval times[2]),
(fd, times), "futimes")
DLSYM_WRAPPER(int, utimes, (const char* path, const struct timeval times[2]),
(path, times), "utimes")
// listxattr
DLSYM_WRAPPER(ssize_t, listxattr, (const char* path, char* list, size_t size),
(path, list, size), "listxattr")
DLSYM_WRAPPER(ssize_t, llistxattr, (const char* path, char* list, size_t size),
(path, list, size), "llistxattr")
DLSYM_WRAPPER(ssize_t, flistxattr, (int fd, char* list, size_t size),
(fd, list, size), "flistxattr")
// DLSYM_WRAPPER(long, syscall, (long number, char* cmd, void* arg, void* env),
// (number, cmd, arg, env), "syscall")
DLSYM_WRAPPER(DIR*, fdopendir, (int fd), (fd), "fdopendir")
DLSYM_WRAPPER(int, scandir,
(const char* dirp, struct dirent*** namelist,
typeof(int(const struct dirent*))* filter,
typeof(int(const struct dirent**,
const struct dirent**))* compar),
(dirp, namelist, filter, compar), "scandir")
DLSYM_WRAPPER(int, symlink, (const char* path1, const char* path2),
(path1, path2), "symlink")
// NEED HAS_SYMLINKS
DLSYM_WRAPPER(ssize_t, readlink, (const char* path, char* buf, size_t bufsize),
(path, buf, bufsize), "readlink")
DLSYM_WRAPPER(ssize_t, readlinkat,
(int dfd, const char* path, char* buf, size_t bufsize),
(dfd, path, buf, bufsize), "readlinkat")
/*
CDLSYM_WRAPPER(int, openat2,
(int dfd, const char* path, int flags, mode_t mode),
(dfd, path, flags, mode), "openat2")
CDLSYM_WRAPPER(int, execve,
(const char* filename, char* const argv, char* const envp),
(filename, argv, envp), "execve")
CDLSYM_WRAPPER(int, execveat,
(int dfd, const char* filename, char* const argv[],
char* const envp[], int flags),
(dfd, filename, argv, envp, flags), "execveat")
CDLSYM_WRAPPER(int, execv, (const char* filename, char* const argv[]),
(filename, argv), "execv")
CDLSYM_WRAPPER(int, fexecve, (int fd, const char* filename, char* const argv[]),
(fd, filename, argv), "fexecve")
CDLSYM_WRAPPER(int, execvpe, (const char* filename, char* const argv[], char*
const envp[]), (filename, argv, envp), "execvpe")
CDLSYM_WRAPPER(int, execvp, (const char* filename, char* const
argv[]), (filename, argv), "execvp")
*/
/* not used */
/*static void
convert(struct stat64* src, struct stat* dest) {
dest->st_dev = static_cast<__dev_t>(src->st_dev);
dest->st_ino = static_cast<__ino_t>(src->st_ino);
dest->st_mode = static_cast<__mode_t>(src->st_mode);
dest->st_nlink = static_cast<__nlink_t>(src->st_nlink);
dest->st_uid = static_cast<__uid_t>(src->st_uid);
dest->st_gid = static_cast<__gid_t>(src->st_gid);
dest->st_rdev = static_cast<__dev_t>(src->st_rdev);
dest->st_size = static_cast<__off_t>(src->st_size);
dest->st_blksize = static_cast<__blksize_t>(src->st_blksize);
dest->st_blocks = static_cast<__blkcnt_t>(src->st_blocks);
dest->st_atime = static_cast<__time_t>(src->st_atime);
dest->st_mtime = static_cast<__time_t>(src->st_mtime);
dest->st_ctime = static_cast<__time_t>(src->st_ctime);
}*/
static void
convert(struct stat* src, struct stat64* dest) {
dest->st_dev = static_cast<__dev_t>(src->st_dev);
dest->st_ino = static_cast<__ino64_t>(src->st_ino);
dest->st_mode = static_cast<__mode_t>(src->st_mode);
dest->st_nlink = static_cast<__nlink_t>(src->st_nlink);
dest->st_uid = static_cast<__uid_t>(src->st_uid);
dest->st_gid = static_cast<__gid_t>(src->st_gid);
dest->st_rdev = static_cast<__dev_t>(src->st_rdev);
dest->st_size = static_cast<__off64_t>(src->st_size);
dest->st_blksize = static_cast<__blksize_t>(src->st_blksize);
dest->st_blocks = static_cast<__blkcnt64_t>(src->st_blocks);
dest->st_atime = static_cast<__time_t>(src->st_atime);
dest->st_mtime = static_cast<__time_t>(src->st_mtime);
dest->st_ctime = static_cast<__time_t>(src->st_ctime);
}
//========================= Path Handling ========================//
/**
* @enum PathStatus
* @brief Enum representing the status of a resolved path.
*
* - External: Path is outside of GekkoFS scope.
* - Internal: Path is within GekkoFS scope.
* - Error: An error occurred during path resolution (errno is set).
*/
enum class PathStatus { External, Internal, Error };
/**
* @brief Resolves a path in the context of GekkoFS.
*
* Determines if a given path (relative to a directory file descriptor `dirfd`)
* is managed by GekkoFS or external. If it's a GekkoFS path, it resolves it
* to an internal representation.
*
* @param dirfd Directory file descriptor for relative path resolution (AT_FDCWD
* for current directory).
* @param path The path to resolve.
* @param resolved Output parameter to store the resolved path string.
* @param flags Optional flags (currently unused in the original code, might be
* for future extensions).
* @return PathStatus indicating whether the path is internal, external, or if
* an error occurred. If an error occurs, errno will be set to indicate the
* specific error (ENOTDIR, EBADF).
*/
PathStatus
resolve_gkfs_path(int dirfd, const char* path, std::string& resolved,
int flags = 0, bool resolve_last_link = true) {
const auto status = CTX->relativize_fd_path(dirfd, path, resolved, flags,
resolve_last_link);
switch(status) {
case gkfs::preload::RelativizeStatus::internal:
return PathStatus::Internal;
case gkfs::preload::RelativizeStatus::fd_not_a_dir:
errno = ENOTDIR;
return PathStatus::Error;
case gkfs::preload::RelativizeStatus::fd_unknown:
errno = EBADF;
return PathStatus::Error;
default:
return PathStatus::External;
}
}
bool
is_gkfs_fd(int fd) {
return CTX->file_map()->exist(fd);
}
#define GKFS_OPERATION(name, ...) \
if(CTX->interception_enabled() && is_gkfs_fd(fd)) { \
\
auto res = gkfs::syscall::gkfs_##name(__VA_ARGS__); \
DEBUG_INFO("[GKFS] {} -> res {}", fd, res); \
return res; \
}
#define GKFS_PATH_OPERATION(name, dirfd, path, ...) \
if(CTX->interception_enabled()) { \
std::string resolved; \
switch(resolve_gkfs_path(dirfd, path, resolved)) { \
case PathStatus::Internal: \
DEBUG_INFO("[GKFS] {}", resolved); \
return gkfs::syscall::gkfs_##name(resolved, __VA_ARGS__); \
case PathStatus::Error: \
return -1; \
default: \
break; \
} \
}
#define GKFS_PATH_OPERATION_DIR(name, dirfd, path, ...) \
if(CTX->interception_enabled()) { \
std::string resolved; \
switch(resolve_gkfs_path(dirfd, path, resolved)) { \
case PathStatus::Internal: \
DEBUG_INFO("[GKFS] {}", resolved); \
return gkfs::syscall::gkfs_##name(dirfd, resolved, \
__VA_ARGS__); \
case PathStatus::Error: \
return -1; \
default: \
break; \
} \
}
#define GKFS_PATH_OPERATION1(name, dirfd, path) \
if(CTX->interception_enabled()) { \
std::string resolved; \
switch(resolve_gkfs_path(dirfd, path, resolved)) { \
case PathStatus::Internal: \
DEBUG_INFO("[GKFS] {}", resolved); \
return gkfs::syscall::gkfs_##name(resolved); \
case PathStatus::Error: \
return -1; \
default: \
break; \
} \
}
#define GKFS_FALLBACK(func_name, ...) return dlsym_##func_name(__VA_ARGS__);
// File API
int
open(const char* path, int flags, ...) {
va_list ap;
va_start(ap, flags);
mode_t mode = va_arg(ap, mode_t);
va_end(ap);
initializeGekko();
GKFS_PATH_OPERATION(open, AT_FDCWD, path, mode, flags)
GKFS_FALLBACK(open2, const_cast<char*>(path), flags, mode)
}
int
open64(const char* path, int flags, ...) {
va_list ap;
va_start(ap, flags);
mode_t mode = va_arg(ap, mode_t);
va_end(ap);
initializeGekko();
GKFS_PATH_OPERATION(open, AT_FDCWD, path, mode, flags)
GKFS_FALLBACK(open64, const_cast<char*>(path), flags, mode)
}
int
open64(const char* path, int flags, mode_t mode) {
initializeGekko();
GKFS_PATH_OPERATION(open, AT_FDCWD, path, mode, flags)
GKFS_FALLBACK(open64, const_cast<char*>(path), flags, mode)
}
int
openat(int dirfd, const char* path, int flags, ...) {
va_list ap;
mode_t mode = 0;
va_start(ap, flags);
mode = va_arg(ap, mode_t);
va_end(ap);
initializeGekko();
GKFS_PATH_OPERATION(open, dirfd, path, mode, flags)
GKFS_FALLBACK(openat, dirfd, path, flags, mode)
}
int
openat64(int dirfd, const char* path, int flags, ...) {
va_list ap;
mode_t mode = 0;
va_start(ap, flags);
mode = va_arg(ap, mode_t);
va_end(ap);
return openat(dirfd, path, flags, mode);
}
// Generate the rest of the functions to build a libc io interception
int
close(int fd) {
initializeGekko();
GKFS_OPERATION(close, fd)
GKFS_FALLBACK(close, fd);
}
std::vector<int>
get_open_fds() {
std::vector<int> fds;
const std::string fd_path = "/proc/self/fd";
DIR* dir = dlsym_opendir(fd_path.c_str());
if(!dir) {
perror("opendir() failed");
return fds;
}
struct dirent* entry;
while((entry = dlsym_readdir(dir))) {
// Skip "." and ".." entries
if(entry->d_type == DT_DIR)
continue;
try {
int fd = std::stoi(entry->d_name);
// Skip the directory FD itself
if(fd != dirfd(dir)) {
fds.push_back(fd);
}
} catch(const std::exception&) {
// Ignore non-integer entries
}
}
dlsym_closedir(dir);
/* fds.erase(std::remove_if(fds.begin(), fds.end(),
[](int fd) { return fcntl(fd, F_GETFD) < 0; }),
fds.end());
*/
return fds;
}
int
close_range(unsigned low, unsigned high, int flags) {
DEBUG_INFO("close_range({}, {}, {})", low, high, flags);
return 0;
auto fds = get_open_fds();
#ifdef CLOSE_RANGE_UNSHARE
if(flags & CLOSE_RANGE_UNSHARE) {
// Unshare the file descriptor table
DEBUG_INFO("close_range with CLOSE_RANGE_UNSHARE");
if(unshare(CLONE_FILES) == -1) {
perror("unshare() failed");
return -1;
}
}
#endif
#ifdef CLOSE_RANGE_CLOEXEC
if(flags & CLOSE_RANGE_CLOEXEC) {
// Close all file descriptors in the range
DEBUG_INFO("close_range with CLOSE_RANGE_CLOEXEC");
}
#endif
// Is fd from gekkofs ?
initializeGekko();
if(CTX->interception_enabled()) {
for(unsigned int i : fds) {
if(i < low || i > high)
continue;
if(is_gkfs_fd(i)) {
DEBUG_INFO("[GKFS] Closing fd {}", i);
gkfs::syscall::gkfs_close(i);
} else {
DEBUG_INFO("[NONGKFS] Closing fd {}", i);
close(i);
}
}
return 0;
} else {
std::cout << "Not in gekko: close_range" << std::endl;
for(unsigned int i : fds) {
if(i < low || i > high)
continue;
std::cout << "Not in gekko: close_range " << i << std::endl;
close(i);
}
}
// We are not in gekko just do it...
// GKFS_FALLBACK(close_range, low, high, flags);
return 0;
}
int
creat(const char* path, mode_t mode) {
initializeGekko();
GKFS_PATH_OPERATION(create, AT_FDCWD, path, mode)
GKFS_FALLBACK(creat, path, mode);
}
int
ftruncate(int fd, off_t length) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
std::string path_str = CTX->file_map()->get(fd)->path();
DEBUG_INFO("[GKFS] {}", path_str);
return gkfs::syscall::gkfs_truncate(path_str, length);
}
GKFS_FALLBACK(ftruncate, fd, length);
}
ssize_t
read(int fd, void* buf, size_t nbyte) {
initializeGekko();
GKFS_OPERATION(read, fd, buf, nbyte);
GKFS_FALLBACK(read, fd, buf, nbyte);
}
ssize_t
write(int fd, const void* buf, size_t nbyte) {
initializeGekko();
GKFS_OPERATION(write, fd, buf, nbyte);
GKFS_FALLBACK(write, fd, buf, nbyte);
}
int
mkdir(const char* path, mode_t mode) {
initializeGekko();
DEBUG_INFO("[GKFS] at MKDIR path {} {}", path, CTX->interception_enabled());
if(CTX->interception_enabled()) {
std::string resolved;
DEBUG_INFO("[GKFS] mkdir path {}", path);
switch(resolve_gkfs_path(AT_FDCWD, path, resolved)) {
case PathStatus::Internal:
DEBUG_INFO("[GKFS] mkdir res {}", resolved);
return gkfs::syscall::gkfs_create(resolved, mode | S_IFDIR);
case PathStatus::Error:
return -1;
default:
break;
}
}
GKFS_FALLBACK(mkdir, path, mode);
}
int
mkdirat(int dirfd, const char* path, mode_t mode) {
initializeGekko();
GKFS_PATH_OPERATION(create, dirfd, path, mode | S_IFDIR)
GKFS_FALLBACK(mkdirat, dirfd, path, mode);
}
int
rmdir(const char* path) {
initializeGekko();
GKFS_PATH_OPERATION1(rmdir, AT_FDCWD, path)
GKFS_FALLBACK(rmdir, path);
}
ssize_t
pread(int fd, void* buf, size_t count, off_t offset) {
initializeGekko();
GKFS_OPERATION(pread, fd, buf, count, offset);
GKFS_FALLBACK(pread, fd, buf, count, offset);
}
ssize_t
pwrite(int fd, const void* buf, size_t count, off_t offset) {
initializeGekko();
GKFS_OPERATION(pwrite, fd, buf, count, offset);
GKFS_FALLBACK(pwrite, fd, buf, count, offset);
}
int
mkstemp(char* templates) {
GKFS_FALLBACK(mkstemp, templates);
}
off_t
lseek(int fd, off_t offset, int whence) {
initializeGekko();
GKFS_OPERATION(lseek, fd, offset, whence);
GKFS_FALLBACK(lseek, fd, offset, whence);
}
off64_t
lseek64(int fd, off64_t offset, int whence) {
initializeGekko();
GKFS_OPERATION(lseek, fd, offset, whence);
GKFS_FALLBACK(lseek64, fd, offset, whence);
}
ssize_t
pread64(int fd, void* buf, size_t count, off64_t offset) {
initializeGekko();
GKFS_OPERATION(pread, fd, buf, count, offset);
GKFS_FALLBACK(pread64, fd, buf, count, offset);
}
ssize_t
pwrite64(int fd, const void* buf, size_t count, off64_t offset) {
initializeGekko();
GKFS_OPERATION(pwrite, fd, buf, count, offset);
GKFS_FALLBACK(pwrite64, fd, buf, count, offset);
}
int
remove(const char* path) {
initializeGekko();
GKFS_PATH_OPERATION1(libcremove, AT_FDCWD, path)
GKFS_FALLBACK(remove, path);
}
int
unlink(const char* path) {
initializeGekko();
GKFS_PATH_OPERATION1(remove, AT_FDCWD, path)
GKFS_FALLBACK(unlink, path);
}
int
__xstat(int ver, const char* path, struct stat* buf) {
GKFS_PATH_OPERATION(stat, AT_FDCWD, path, buf)
auto res = dlsym_stat(ver, path, buf);
return res;
}
int
__xstat64(int ver, const char* path, struct stat64* buf) {
struct stat st;
if(CTX->interception_enabled()) {
std::string resolved;
switch(resolve_gkfs_path(AT_FDCWD, path, resolved)) {
case PathStatus::Internal: {
DEBUG_INFO("[GKFS] {}", resolved);
auto res = gkfs::syscall::gkfs_stat(resolved, &st);
convert(&st, buf);
return res;
}
case PathStatus::Error: {
return -(*__errno_location());
}
default: {
}
}
}
auto res = dlsym_stat(ver, path, &st);
convert(&st, buf);
return res;
}
int
statx(int dirfd, const char* path, int flags, unsigned int mask,
struct statx* statxbuf) {
initializeGekko();
bool follow = (flags & AT_SYMLINK_NOFOLLOW) == 0;
DEBUG_INFO("[GKFS] {} -> follow {}", path, follow);
GKFS_PATH_OPERATION_DIR(statx, dirfd, path, flags, mask, statxbuf, follow)
GKFS_FALLBACK(statx, dirfd, path, flags, mask, statxbuf);
}
/**
* @brief lxstat wrapper function. Should be called with resolve_last_link =
* false on the CI.
*
* @param ver
* @param path
* @param buf
* @return int
*/
int
__lxstat(int ver, const char* path, struct stat* buf) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
switch(resolve_gkfs_path(AT_FDCWD, path, resolved, 0, false)) {
case PathStatus::Internal: {
DEBUG_INFO("[GKFS] {}", resolved);
auto res = gkfs::syscall::gkfs_stat(resolved, buf, false);
return res;
}
case PathStatus::Error: {
return -(*__errno_location());
}
default: {
}
}
}
GKFS_FALLBACK(__lxstat, ver, path, buf);
}
int
__lxstat64(int ver, const char* path, struct stat64* buf) {
initializeGekko();
struct stat st;
if(CTX->interception_enabled()) {
std::string resolved;
switch(resolve_gkfs_path(AT_FDCWD, path, resolved)) {
case PathStatus::Internal: {
DEBUG_INFO("[GKFS] {}", resolved);
auto res = gkfs::syscall::gkfs_stat(resolved, &st, false);
convert(&st, buf);
return res;
}
case PathStatus::Error: {
return -(*__errno_location());
}
default: {
}
}
}
GKFS_FALLBACK(lxstat64, ver, path, buf);
}
int
__fxstat(int ver, int fd, struct stat* buf) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
return gkfs::syscall::gkfs_stat(CTX->file_map()->get(fd)->path(), buf,
true, true);
}
GKFS_FALLBACK(__fxstat, ver, fd, buf);
}
int
__fxstatat(int ver, int dfd, const char* path, struct stat* buf, int flags) {
initializeGekko();
bool follow = (flags & AT_SYMLINK_NOFOLLOW) == 0;
GKFS_PATH_OPERATION(stat, AT_FDCWD, path, buf, follow)
GKFS_FALLBACK(fxstatat, ver, dfd, path, buf, flags);
}
int
__fxstat64(int ver, int fd, struct stat64* buf) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
struct stat st;
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
int res = gkfs::syscall::gkfs_stat(CTX->file_map()->get(fd)->path(),
&st, true, true);
if(res == 0)
convert(&st, buf);
return res;
}
GKFS_FALLBACK(fxstat64, ver, fd, buf);
}
int
fstat64(int fd, struct stat64* buf) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
struct stat st;
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
int res = gkfs::syscall::gkfs_stat(CTX->file_map()->get(fd)->path(),
&st, true, true);
if(res == 0)
convert(&st, buf);
return res;
}
GKFS_FALLBACK(fstat64, fd, buf);
}
int
fstat(int fd, struct stat* buf) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
// The fd could be a renamed file, thus when doing gkfs_stat we will get
// a is not found.
int res = gkfs::syscall::gkfs_stat(CTX->file_map()->get(fd)->path(),
buf, true, true);
return res;
}
GKFS_FALLBACK(fstat, fd, buf);
}
int
__fxstatat64(int ver, int dfd, const char* path, struct stat64* buf,
int flags) {
initializeGekko();
struct stat st;
if(CTX->interception_enabled()) {
std::string resolved;
switch(resolve_gkfs_path(dfd, path, resolved)) {
case PathStatus::Internal: {
DEBUG_INFO("[GKFS] {}", resolved);
bool follow = (flags & AT_SYMLINK_NOFOLLOW) == 0;
auto res = gkfs::syscall::gkfs_stat(resolved, &st, follow);
convert(&st, buf);
return res;
}
case PathStatus::Error: {
return -(*__errno_location());
}
default: {
}
}
}
GKFS_FALLBACK(fstatat64, dfd, path, buf, flags);
}
int
stat(const char* path, struct stat* buf) {
initializeGekko();
GKFS_PATH_OPERATION(stat, AT_FDCWD, path, buf)
GKFS_FALLBACK(stat, _STAT_VER, path, buf);
}
int
stat64(const char* path, struct stat64* buf) {
initializeGekko();
struct stat st;
if(CTX->interception_enabled()) {
std::string resolved;
switch(resolve_gkfs_path(AT_FDCWD, path, resolved)) {
case PathStatus::Internal: {
DEBUG_INFO("[GKFS] {}", resolved);
auto res = gkfs::syscall::gkfs_stat(resolved, &st);
convert(&st, buf);
return res;
}
case PathStatus::Error: {
return -(*__errno_location());
}
default: {
}
}
}
int ret = dlsym_stat(_STAT_VER, path, &st);
if(ret == 0)
convert(&st, buf);
return ret;
}
int
fstatat(int dfd, const char* path, struct stat* buf, int flags) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
switch(resolve_gkfs_path(dfd, path, resolved)) {
case PathStatus::Internal: {
DEBUG_INFO("[GKFS] {}", resolved);
bool follow = (flags & AT_SYMLINK_NOFOLLOW) == 0;
auto res = gkfs::syscall::gkfs_stat(resolved, buf, follow);
return res;
}
case PathStatus::Error: {
return -(*__errno_location());
}
default: {
}
}
}
GKFS_FALLBACK(fstatat, dfd, path, buf, flags);
}
int
rename(const char* oldpath, const char* newpath) {
initializeGekko();
// Is path from GekkoFS?
DEBUG_INFO("rename {} --> {}", oldpath, newpath);
if(CTX->interception_enabled()) {
std::string resolved_old;
std::string resolved_new;
auto rstatus_old = resolve_gkfs_path(AT_FDCWD, oldpath, resolved_old);
auto rstatus_new = resolve_gkfs_path(AT_FDCWD, newpath, resolved_new);
switch(rstatus_old) {
case PathStatus::Internal:
switch(rstatus_new) {
case PathStatus::Internal:
DEBUG_INFO("[GKFS] {} {}", resolved_old, resolved_new);
#ifdef HAS_RENAME
return gkfs::syscall::gkfs_rename(resolved_old,
resolved_new);
#else
errno = ENOTSUP;
return -1;
#endif
default:
// Try normal open.
break;
}
default:
// Try normal open.
break;
}
}
GKFS_FALLBACK(rename, oldpath, newpath);
}
/*
pid_t
fork(void) {
printf("[BYPASS] >> Begin fork() %p\n", (void*) &activated);
pid_t ret = dlsym_fork();
if(0 == ret) {
// We want the children to be initialized
printf("%d -> %d I am the child %p\n", ret, getpid(),
(void*) &activated);
// DEBUG_INFO("[BYPASS] >> ACTIVATED GEKKOFS (child).... \n");
// initializing = false;
// init_libc();
// DEBUG_INFO("%d -> %d [BYPASS] << After fork(child)\n", ret,
// getpid());
return ret;
}
// initializing = false;
// init_libc();
printf("%d -> %d [BYPASS] << After fork() %p\n", ret, getpid(),
(void*) &activated);
return ret;
}
*/
/*
pid_t
vfork(void) {
printf("[BYPASS] >> Begin vfork() %p\n", (void*) &activated);
pid_t ret = dlsym_vfork();
if(0 == ret) {
// We want the children to be initialized
// DEBUG_INFO("[BYPASS] >> ACTIVATED GEKKOFS (child).... \n");
// initializing = false;
//at_child();
printf("%d -> %d I am the child vfork\n", ret, getpid());
return ret;
}
// initializing = false;
return ret;
}
*/
int
pipe(int pipefd[2]) {
initializeGekko();
auto res = dlsym_pipe(pipefd);
DEBUG_INFO("pipe {} <---> {}", pipefd[0], pipefd[1]);
// GKFS_FALLBACK(pipe, pipefd);
return res;
}
int
dup(int fd) {
initializeGekko();
GKFS_OPERATION(dup, fd);
GKFS_FALLBACK(dup, fd);
}
int
dup2(int fd, int fd2) {
initializeGekko();
if(CTX->interception_enabled()) {
if(is_gkfs_fd(fd) && is_gkfs_fd(fd2)) {
DEBUG_INFO("[GKFS] DUP2 G{} --> G{}", fd, fd2);
return gkfs::syscall::gkfs_dup2(fd, fd2);
} else if(is_gkfs_fd(fd2)) {
DEBUG_INFO("[GKFS-NON] DUP2 {} --> G{}", fd, fd2);
return gkfs::syscall::gkfs_dup(fd);
} else if(is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS-NON] DUP2 G{} --> {}", fd, fd2);
return gkfs::syscall::gkfs_dup2(fd, fd2);
}
}
// GKFS_OPERATION(dup2, fd, fd2);
GKFS_FALLBACK(dup2, fd, fd2);
}
// TODO: Implement CLOEXEC
int
dup3(int fd, int fd2, int flags) {
initializeGekko();
GKFS_OPERATION(dup2, fd, fd2);
GKFS_FALLBACK(dup3, fd, fd2, flags);
}
void
exit(int status) {
dlsym_exit(status);
__builtin_unreachable();
}
// Manager API
int
chdir(const char* path) {
initializeGekko();
// is the path from gekkofs
if(CTX->interception_enabled()) {
std::string resolved;
auto rstatus = CTX->relativize_fd_path(AT_FDCWD, path, resolved);
DEBUG_INFO("[GKFS] chdir status {} {} --> {}", (int) rstatus, resolved,
path);
switch(rstatus) {
case gkfs::preload::RelativizeStatus::fd_not_a_dir:
return -ENOTDIR;
case gkfs::preload::RelativizeStatus::internal: {
auto res = gkfs::hook::hook_chdir(path);
if(res < 0) {
errno = -res;
return -1;
}
return 0;
} break;
default:
// Try normal open.
break;
}
}
CTX->cwd(path);
GKFS_FALLBACK(chdir, const_cast<char*>(path));
}
int
fchdir(int fd) {
initializeGekko();
// is the path from gekkofs
auto res = gkfs::hook::hook_fchdir(fd);
if(res < 0) {
errno = -res;
return -1;
}
return 0;
}
int
fcntl(int fd, int cmd, ...) // TODO
{
initializeGekko();
if(!real_fcntl) {
real_fcntl = reinterpret_cast<int (*)(int fd, int cmd, ...)>(
dlsym(((void*) -1l), "fcntl"));
if(!real_fcntl) {
fprintf(stderr, "dlsym failed for %s: %s\n", "fcntl", dlerror());
(*__errno_location()) = 38;
return (int) -1;
}
}
va_list myargs;
va_start(myargs, cmd);
auto arg = va_arg(myargs, long);
va_end(myargs);
// is from gekkofs?
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
int ret = -1;
switch(cmd) {
case F_DUPFD:
DEBUG_INFO("[GKFS] DUPFD {}", fd);
return gkfs::syscall::gkfs_dup(fd);
case F_DUPFD_CLOEXEC:
DEBUG_INFO("[GKFS] F_DUPFD_CLOEXEC {}", fd);
ret = gkfs::syscall::gkfs_dup(fd);
if(ret == -1) {
return -1;
}
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::cloexec, true);
return ret;
case F_GETFD:
DEBUG_INFO("[GKFS] F_GETFD {}", fd);
if(CTX->file_map()->get(fd)->get_flag(
gkfs::filemap::OpenFile_flags::cloexec)) {
return FD_CLOEXEC;
}
return 0;
case F_GETFL:
DEBUG_INFO("[GKFS] F_GETFL {}", 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_SETFL:
DEBUG_INFO("[GKFS] F_SETFL {}", fd);
ret = 0;
// get flags from arg and setup
if(arg & O_RDONLY) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::rdonly, true);
}
if(arg & O_WRONLY) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::wronly, true);
}
if(arg & O_RDWR) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::rdwr, true);
}
if(arg & O_APPEND) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::append, true);
}
if(arg & O_NONBLOCK) {
DEBUG_INFO("[GKFS] F_SETFL {} NONBLOCK", fd);
}
return ret;
case F_SETFD:
DEBUG_INFO("[GKFS] F_SETFD {}", fd);
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::cloexec,
(arg & FD_CLOEXEC));
return 0;
#ifdef ENABLE_LOCKING
case F_GETLK: {
DEBUG_INFO("[GKFS] F_GETLK {}", fd);
auto res = real_fcntl(fd, cmd, arg);
return res;
}
case F_SETLK: {
DEBUG_INFO("[GKFS] F_SETLK {}", fd);
auto res = real_fcntl(fd, cmd, arg);
return res;
}
case F_SETLKW: // Used on S3D-IO
{
DEBUG_INFO("[GKFS] F_SETLKW {}", fd);
auto res = real_fcntl(fd, cmd, arg);
return res;
}
case F_GETOWN: {
DEBUG_INFO("[GKFS] F_GETOWN {}", fd);
auto res = real_fcntl(fd, cmd, arg);
return res;
}
case F_SETOWN: // Used on S3D-IO
{
DEBUG_INFO("[GKFS] F_SETOWN {}", fd);
auto res = real_fcntl(fd, cmd, arg);
return res;
}
#endif
default:
DEBUG_INFO("[GKFS] NOTSUPPORTED {}", fd);
errno = ENOTSUP;
return -1;
}
}
// log_arguments("fcntl", fd, cmd);
auto res = real_fcntl(fd, cmd, arg);
return res;
// GKFS_FALLBACK(fcntl, fd, cmd, arg);
}
int
access(const char* path, int mode) {
initializeGekko();
GKFS_PATH_OPERATION(access, AT_FDCWD, path, mode, true)
GKFS_FALLBACK(access, path, mode);
}
char*
realpath(const char* path, char* resolved_path) {
// is gekkofs
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
auto rstatus = CTX->relativize_fd_path(AT_FDCWD, path, resolved);
switch(rstatus) {
case gkfs::preload::RelativizeStatus::fd_not_a_dir:
return nullptr;
case gkfs::preload::RelativizeStatus::internal:
DEBUG_INFO("[GKFS] {} --> {}", resolved, path);
if(resolved_path == nullptr) {
resolved_path =
static_cast<char*>(malloc(resolved.size() + 1));
if(resolved_path == nullptr) {
errno = ENOMEM;
return nullptr;
}
}
strcpy(resolved_path, path);
return resolved_path;
default:
break;
}
}
GKFS_FALLBACK(realpath, path, resolved_path);
}
char*
__realpath_chk(const char* path, char* resolved_path,
__attribute__((__unused__)) size_t resolved_len) {
return realpath(path, resolved_path);
}
int
fsync(int fd) // TODO
{
GKFS_FALLBACK(fsync, fd);
}
int
flock(int fd, int operation) {
GKFS_FALLBACK(flock, fd, operation);
}
DIR*
gekko_opendir(const std::string& path) {
struct stat st;
if(gkfs::syscall::gkfs_stat(path, &st) != 0 || S_ISREG(st.st_mode)) {
errno = ENOTDIR;
return nullptr;
}
const int fd = gkfs::syscall::gkfs_opendir(path);
if(fd < 0)
return nullptr;
DIR* dirp = static_cast<DIR*>(malloc(sizeof(DIR)));
if(dirp == NULL) {
errno = ENOMEM;
return NULL;
}
dirp->fd = fd;
dirp->path = strdup(path.c_str());
return dirp;
}
DIR*
opendir(const char* dirname) {
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, dirname, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
return gekko_opendir(resolved);
}
}
return dlsym_opendir(const_cast<char*>(dirname));
}
// opendir64
DIR*
opendir64(const char* dirname) {
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, dirname, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
return gekko_opendir(resolved);
}
}
return dlsym_opendir64(const_cast<char*>(dirname));
}
DIR*
fdopendir(int fd) {
if(CTX->interception_enabled()) {
if(is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
return gekko_opendir(CTX->file_map()->get(fd)->path());
}
}
GKFS_FALLBACK(fdopendir, fd);
}
// clang-format off
#ifndef __ALIGN_KERNEL_MASK
#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
#endif
#ifndef __ALIGN_KERNEL
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x)) (a) -1)
#endif
// clang-format on
#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
// readdir
struct dirent*
readdir(DIR* dirp) {
struct dirent* ret;
initializeGekko();
if(CTX->interception_enabled()) {
if(CTX->file_map()->exist(dirp->fd)) {
// call gekko getdents
// DEBUG_INFO("[BYPASS] >> readdir GKFS.... %p\n", (void*)
// dirp);
ret = static_cast<struct dirent*>(malloc(sizeof(dirent) * 1));
auto open_dir = CTX->file_map()->get_dir(dirp->fd);
if(open_dir == nullptr) {
// Cast did not succeeded: open_file is a regular file
errno = EBADF;
free(static_cast<struct dirent*>(ret));
return NULL;
}
// get directory position of which entries to return
auto pos = open_dir->pos();
if(pos >= open_dir->size()) {
free(static_cast<struct dirent*>(ret));
return NULL;
}
auto de = open_dir->getdent(pos);
auto total_size = ALIGN(offsetof(struct dirent, d_name) +
de.name().size() + 3,
sizeof(long));
// fill ret with data
ret->d_ino = std::hash<std::string>()(open_dir->path() + "/" +
de.name());
ret->d_reclen = total_size;
ret->d_type =
((de.type() == gkfs::filemap::FileType::regular) ? DT_REG
: DT_DIR);
std::strcpy(&(ret->d_name[0]), de.name().c_str());
open_dir->pos(pos + 1);
return ret;
}
}
ret = dlsym_readdir(dirp);
return ret;
}
// readdir64
struct dirent64*
readdir64(DIR* dirp) {
struct dirent64* ret;
initializeGekko();
if(CTX->interception_enabled()) {
if(CTX->file_map()->exist(dirp->fd)) {
// call gekko getdents
// DEBUG_INFO("[BYPASS] >> readdir64 GKFS.... %p\n", (void*)
// dirp);
ret = static_cast<struct dirent64*>(malloc(sizeof(dirent64) * 1));
auto open_dir = CTX->file_map()->get_dir(dirp->fd);
if(open_dir == nullptr) {
// Cast did not succeeded: open_file is a regular file
errno = EBADF;
free(static_cast<struct dirent64*>(ret));
return NULL;
}
// get directory position of which entries to return
auto pos = open_dir->pos();
if(pos >= open_dir->size()) {
free(static_cast<struct dirent64*>(ret));
return NULL;
}
auto de = open_dir->getdent(pos);
auto total_size = ALIGN(offsetof(struct dirent64, d_name) +
de.name().size() + 3,
sizeof(long));
// fill ret with data
ret->d_ino = std::hash<std::string>()(open_dir->path() + "/" +
de.name());
ret->d_reclen = total_size;
ret->d_type =
((de.type() == gkfs::filemap::FileType::regular) ? DT_REG
: DT_DIR);
std::strcpy(&(ret->d_name[0]), de.name().c_str());
open_dir->pos(pos + 1);
return ret;
}
}
ret = dlsym_readdir64(dirp);
return ret;
}
// closedir
int
closedir(DIR* dirp) {
int ret;
initializeGekko();
if(CTX->interception_enabled()) {
if(CTX->file_map()->exist(dirp->fd)) {
DEBUG_INFO("[GKFS] {}", dirp->fd);
ret = gkfs::syscall::gkfs_close(dirp->fd);
free(dirp->path);
free(dirp);
return ret;
}
}
ret = dlsym_closedir(dirp);
return ret;
}
// telldir
long
telldir(DIR* dirp) {
long ret;
initializeGekko();
if(CTX->interception_enabled()) {
if(CTX->file_map()->exist(dirp->fd)) {
DEBUG_INFO("[GKFS] NOT IMPLEMENTED");
ret = 0;
// ret = gkfs::syscall::gkfs_telldir(dirp->fd);
return ret;
}
}
ret = dlsym_telldir(dirp);
return ret;
}
// seekdir
void
seekdir(DIR* dirp, long loc) {
initializeGekko();
if(CTX->interception_enabled()) {
if(CTX->file_map()->exist(dirp->fd)) {
DEBUG_INFO("[GKFS] NOT IMPLEMENTED");
return;
}
}
dlsym_seekdir(dirp, loc);
}
// File ops
FILE*
fdopen(int fd, const char* mode) {
initializeGekko();
if(CTX->interception_enabled()) {
if(is_gkfs_fd(fd)) {
FILE* ret = static_cast<FILE*>(malloc(sizeof(FILE)));
if(ret == NULL) {
errno = ENOMEM;
return NULL;
}
ret->_mode = 0666;
ret->_flags = 0x2000;
if(strchr(mode, 'r') != NULL)
ret->_flags |= O_RDONLY;
if(strchr(mode, 'w'))
ret->_flags = O_WRONLY | O_CREAT | O_TRUNC | 0x2000;
if(strchr(mode, 'a'))
ret->_flags = O_WRONLY | O_CREAT | O_APPEND | 0x2000;
if(strchr(mode, '+'))
ret->_flags = O_RDWR | O_CREAT | 0x2000;
ret->_fileno = fd;
return ret;
}
}
GKFS_FALLBACK(fdopen, fd, mode);
}
FILE*
fopen(const char* path, const char* mode) {
// DEBUG_INFO("[BYPASS] >> fopen.... %s \n", path);
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path, resolved) ==
PathStatus::Internal) {
int flags = 0;
if(strchr(mode, 'r') != NULL)
flags |= O_RDONLY;
if(strchr(mode, 'w'))
flags = O_WRONLY | O_CREAT | O_TRUNC;
if(strchr(mode, 'a'))
flags = O_WRONLY | O_CREAT | O_APPEND;
if(strchr(mode, '+'))
flags = O_RDWR | O_CREAT;
DEBUG_INFO("[GEKKO] {}", resolved);
const int fd = gkfs::syscall::gkfs_open(resolved, 0666, flags);
if(fd < 0)
return nullptr;
FILE* fp = fdopen(fd, mode);
return fp;
}
}
GKFS_FALLBACK(fopen, path, mode);
}
FILE*
freopen64(const char* path, const char* mode, FILE* stream) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path, resolved) ==
PathStatus::Internal) {
int flags = 0;
if(strchr(mode, 'r') != NULL)
flags |= O_RDONLY;
if(strchr(mode, 'w'))
flags = O_WRONLY | O_CREAT | O_TRUNC;
if(strchr(mode, 'a'))
flags = O_WRONLY | O_CREAT | O_APPEND;
if(strchr(mode, '+'))
flags = O_RDWR | O_CREAT;
DEBUG_INFO("[GEKKO] {}", resolved);
const int fd = gkfs::syscall::gkfs_open(resolved, 0666, flags);
if(fd < 0)
return nullptr;
stream->_mode = 0666;
stream->_flags = flags | 0x2000;
stream->_fileno = fd;
return stream;
}
}
GKFS_FALLBACK(freopen64, path, mode, stream);
}
/* We return nmems, not size*/
size_t
fread(void* ptr, size_t size, size_t nmemb, FILE* stream) {
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GEKKO] {}", stream->_fileno);
return gkfs::syscall::gkfs_read(stream->_fileno, ptr, size * nmemb) /
size;
}
GKFS_FALLBACK(fread, ptr, size, nmemb, stream)
}
size_t
fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GEKKO] {}", stream->_fileno);
return gkfs::syscall::gkfs_write(stream->_fileno, ptr, size * nmemb) /
size;
}
GKFS_FALLBACK(fwrite, ptr, size, nmemb, stream)
}
int
fseek(FILE* stream, long int offset, int whence) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GKFS] {}", stream->_fileno);
return gkfs::syscall::gkfs_lseek(stream->_fileno, offset, whence);
}
GKFS_FALLBACK(fseek, stream, offset, whence)
}
long
ftell(FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GKFS] {}", stream->_fileno);
return gkfs::syscall::gkfs_lseek(stream->_fileno, 0, SEEK_CUR);
}
GKFS_FALLBACK(ftell, stream)
}
void
rewind(FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GKFS] {}", stream->_fileno);
gkfs::syscall::gkfs_lseek(stream->_fileno, 0, SEEK_SET);
return;
}
GKFS_FALLBACK(rewind, stream)
}
// clearerr
void
clearerr(FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GKFS] {}", stream->_fileno);
return;
}
GKFS_FALLBACK(clearerr, stream)
}
int
fclose(FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GKFS] {}", stream->_fileno);
auto ret = gkfs::syscall::gkfs_close(stream->_fileno);
// should I do a fclose (stream)?
free(stream);
return ret;
}
GKFS_FALLBACK(fclose, stream)
}
// feof implementation with lseek
int
feof(FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
DEBUG_INFO("[GKFS] {}", stream->_fileno);
off_t cur = gkfs::syscall::gkfs_lseek(stream->_fileno, 0, SEEK_CUR);
off_t end = gkfs::syscall::gkfs_lseek(stream->_fileno, 0, SEEK_END);
gkfs::syscall::gkfs_lseek(stream->_fileno, cur, SEEK_SET);
return (cur == end);
}
GKFS_FALLBACK(feof, stream)
}
int
fputs(const char* str, FILE* stream) {
initializeGekko();
auto fd = stream->_fileno;
GKFS_OPERATION(write, fd, str, strlen(str))
GKFS_FALLBACK(fputs, str, stream)
}
char*
fgets(char* str, int n, FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
// read str n chars
DEBUG_INFO("[GKFS] {}", stream->_fileno);
auto l = gkfs::syscall::gkfs_read(stream->_fileno, str, n);
if(l == 0) {
return NULL;
}
return str;
}
GKFS_FALLBACK(fgets, str, n, stream)
}
// implement fflush and bypass if gekko (no need to log)
int
fflush(FILE* stream) {
initializeGekko();
if(CTX->interception_enabled() && CTX->file_map()->exist(stream->_fileno)) {
// flush stream with gkfs_fsync
return 0;
}
return dlsym_fflush(stream);
}
void*
mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
// create a malloc of length
void* ptr = malloc(length);
if(ptr == nullptr) {
return MAP_FAILED;
}
// store info on mmap_set
mmap_set.insert(std::make_tuple(ptr, fd, length, offset));
gkfs::syscall::gkfs_pread(fd, ptr, length, offset);
return ptr;
}
GKFS_FALLBACK(mmap, addr, length, prot, flags, fd, offset);
}
// implement msync, if addr is from gekkofs (we should have a set with them)
// We pwrite length to the original offset (also from the set), and return
// if not just go to the normal msync
int
msync(void* addr, size_t length, int flags) {
initializeGekko();
// check if addr is from gekkofs (mmap_set)
// if so, get the fd and offset
// pwrite length to the original offset
// return
// if not just go to the normal msync
for(const auto& tuple : mmap_set) {
if(std::get<0>(tuple) == addr) {
int fd = std::get<1>(tuple);
off_t offset = std::get<3>(tuple);
gkfs::syscall::gkfs_pwrite(fd, addr, length, offset);
return 0;
}
}
GKFS_FALLBACK(msync, addr, length, flags);
}
// implement munmap, if it's on the set, call msync, free memory
int
munmap(void* addr, size_t length) {
initializeGekko();
// check if addr is from gekkofs (mmap_set)
// if so, call msync
// free memory
// return
// if not just go to the normal munmap
if(mmap_set.size() != 0) {
for(auto it = mmap_set.begin(); it != mmap_set.end(); ++it) {
if(std::get<0>(*it) == addr) {
msync(addr, length, 0);
free(addr);
mmap_set.erase(it);
return 0;
}
}
}
GKFS_FALLBACK(munmap, addr, length);
}
// fchown
int
fchown(int fd, uid_t owner, gid_t group) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
return 0;
}
GKFS_FALLBACK(fchown, fd, owner, group);
}
// fchmod
int
fchmod(int fd, mode_t mode) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
return 0;
}
GKFS_FALLBACK(fchmod, fd, mode);
}
// futimes
int
futimes(int fd, const struct timeval tv[2]) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
return 0;
}
GKFS_FALLBACK(futimes, fd, tv);
}
int
utimes(const char* path, const struct timeval tv[2]) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
return 0;
}
}
GKFS_FALLBACK(utimes, path, tv);
}
// Without use... goes directly to the syscall (ls -l)
ssize_t
listxattr(const char* path, char* list, size_t size) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
return 0;
}
}
GKFS_FALLBACK(listxattr, path, list, size);
}
ssize_t
flistxattr(int fd, char* list, size_t size) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(fd)) {
DEBUG_INFO("[GKFS] {}", CTX->file_map()->get(fd)->path());
return 0;
}
GKFS_FALLBACK(flistxattr, fd, list, size);
}
ssize_t
llistxattr(const char* path, char* list, size_t size) {
initializeGekko();
return 0;
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
return 0;
}
}
GKFS_FALLBACK(llistxattr, path, list, size);
}
int
lstat(const char* path, struct stat* st) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path, resolved, 0, false) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
auto res = gkfs::syscall::gkfs_stat(resolved, st, false);
return res;
}
}
GKFS_FALLBACK(lstat, path, st);
}
int
lstat64(const char* path, struct stat64* buf) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
struct stat st;
if(resolve_gkfs_path(AT_FDCWD, path, resolved, 0, false) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
auto res = gkfs::syscall::gkfs_stat(resolved, &st, false);
convert(&st, buf);
return res;
}
}
GKFS_FALLBACK(lstat64, path, buf);
}
int
scandir(const char* dirname, struct dirent*** namelist,
int (*filter)(const struct dirent*),
int (*compar)(const struct dirent**, const struct dirent**)) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, dirname, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
// implement scandir : The scandir() function scans the
// directory dirp, calling filter() on each directory entry.
// Entries for which filter() returns nonzero are stored in
// strings allocated via malloc(3), sorted using qsort(3) with
// the comparison function compar(), and collected in
// array namelist which is allocated via malloc(3). If
// filter is NULL, all entries are selected.
DIR* dirp = gekko_opendir(resolved);
if(dirp == nullptr) {
return -1;
}
std::vector<struct dirent*> entries;
struct dirent* entry;
while((entry = readdir(dirp)) != nullptr) {
if(filter == nullptr || filter(entry) != 0) {
entries.push_back(entry);
}
}
closedir(dirp);
if(entries.empty()) {
*namelist = nullptr;
return 0;
}
if(compar != nullptr) {
qsort(entries.data(), entries.size(), sizeof(struct dirent*),
reinterpret_cast<int (*)(const void*, const void*)>(
compar));
}
*namelist = static_cast<struct dirent**>(
malloc(sizeof(struct dirent*) * (entries.size() + 1)));
if(*namelist == nullptr) {
for(auto e : entries) {
free(e);
}
return -1;
}
for(size_t i = 0; i < entries.size(); ++i) {
(*namelist)[i] = entries[i];
}
(*namelist)[entries.size()] = nullptr;
return entries.size();
}
}
GKFS_FALLBACK(scandir, dirname, namelist, filter, compar);
}
// path2 should not exists (is the symbolic link)
int
symlink(const char* path1, const char* path2) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(AT_FDCWD, path1, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] path 1 internal {}", resolved);
std::string resolved2;
if(resolve_gkfs_path(AT_FDCWD, path2, resolved2) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] path 2 internal {}", resolved2);
#ifdef HAS_SYMLINKS
// In Gekko we invert the parameters.
return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
#else
DEBUG_INFO("[GKFS] symlinks not supported/compiled");
errno = ENOTSUP;
return -1;
#endif
}
}
}
GKFS_FALLBACK(symlink, path1, path2);
}
#ifdef HAS_SYMLINKS
ssize_t
readlink(const char* path, char* buf, size_t bufsize) {
initializeGekko();
GKFS_PATH_OPERATION(readlink, AT_FDCWD, path, buf, bufsize);
GKFS_FALLBACK(readlink, path, buf, bufsize);
}
ssize_t
readlinkat(int dfd, const char* path, char* buf, size_t bufsize) {
initializeGekko();
GKFS_PATH_OPERATION(readlink, dfd, path, buf, bufsize);
GKFS_FALLBACK(readlinkat, dfd, path, buf, bufsize);
}
#endif
ssize_t
readv(int fd, const struct iovec* iov, int iovcnt) {
initializeGekko();
GKFS_OPERATION(readv, fd, iov, iovcnt)
GKFS_FALLBACK(readv, fd, iov, iovcnt)
}
ssize_t
writev(int fd, const struct iovec* iov, int iovcnt) {
initializeGekko();
GKFS_OPERATION(writev, fd, iov, iovcnt)
GKFS_FALLBACK(writev, fd, iov, iovcnt)
}
ssize_t
preadv(int fd, const struct iovec* iov, int iovcnt, off_t offset) {
initializeGekko();
GKFS_OPERATION(preadv, fd, iov, iovcnt, offset)
GKFS_FALLBACK(preadv, fd, iov, iovcnt, offset)
}
ssize_t
pwritev(int fd, const struct iovec* iov, int iovcnt, off_t offset) {
initializeGekko();
GKFS_OPERATION(pwritev, fd, iov, iovcnt, offset)
GKFS_FALLBACK(pwritev, fd, iov, iovcnt, offset)
}
ssize_t
preadv2(int fd, const struct iovec* iov, int iovcnt, off_t offset, int flags) {
initializeGekko();
GKFS_OPERATION(preadv, fd, iov, iovcnt, offset)
GKFS_FALLBACK(preadv2, fd, iov, iovcnt, offset, flags)
}
ssize_t
pwritev2(int fd, const struct iovec* iov, int iovcnt, off_t offset, int flags) {
initializeGekko();
GKFS_OPERATION(pwritev, fd, iov, iovcnt, offset)
GKFS_FALLBACK(pwritev2, fd, iov, iovcnt, offset, flags)
}
// aio Operations (write)
// /*
// #include <aiocb.h>
// struct aiocb {
// int aio_fildes; /* File descriptor */
// off_t aio_offset; /* File offset */
// volatile void *aio_buf; /* Location of buffer */
// size_t aio_nbytes; /* Length of transfer */
// int aio_reqprio; /* Request priority */
// struct sigevent aio_sigevent; /* Notification method */
// int aio_lio_opcode; /* Operation to be
// performed;
// lio_listio() only */
// /* Various implementation-internal fields not shown */
// };
// /* Operation codes for 'aio_lio_opcode': */
// enum { LIO_READ, LIO_WRITE, LIO_NOP };
// Possible values for aio_sigevent.sigev_notify are SIGEV_NONE,
// SIGEV_SIGNAL, and SIGEV_THREAD. See sigevent(3type) for further de‐
// tails.
// */
/* Add result to tracking list */
typedef struct ResultEntry {
struct aiocb* aiocbp;
ssize_t result;
struct ResultEntry* next;
} ResultEntry;
static pthread_mutex_t result_mutex = PTHREAD_MUTEX_INITIALIZER;
static ResultEntry* results = NULL;
static void
add_result(struct aiocb* aiocbp, ssize_t res) {
ResultEntry* entry = (ResultEntry*) malloc(sizeof(ResultEntry));
entry->aiocbp = aiocbp;
entry->result = res;
entry->next = NULL;
pthread_mutex_lock(&result_mutex);
entry->next = results;
results = entry;
pthread_mutex_unlock(&result_mutex);
}
DLSYM_WRAPPER(int, aio_write, (struct aiocb * aiocbp), (aiocbp), "aio_write")
int
aio_write(struct aiocb* aiocbp) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(aiocbp->aio_fildes)) {
auto res = gkfs::syscall::gkfs_write(aiocbp->aio_fildes,
(const void*) aiocbp->aio_buf,
aiocbp->aio_nbytes);
// TODO : SIGNALING IS WRONG*/
add_result(aiocbp, res);
return 0;
}
GKFS_FALLBACK(aio_write, aiocbp)
}
DLSYM_WRAPPER(int, aio_read, (struct aiocb * aiocbp), (aiocbp), "aio_read")
int
aio_read(struct aiocb* aiocbp) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(aiocbp->aio_fildes)) {
auto res = gkfs::syscall::gkfs_read(aiocbp->aio_fildes,
(void*) aiocbp->aio_buf,
aiocbp->aio_nbytes);
// TODO : SIGNALING IS WRONG
add_result(aiocbp, res);
return 0;
}
// aiocbp->aio_nbytes = 0; // TODO : NOt overwrite the buffer
GKFS_FALLBACK(aio_read, aiocbp)
}
/* Find and remove result from tracking list */
static ssize_t
get_result(struct aiocb* aiocbp) {
pthread_mutex_lock(&result_mutex);
ResultEntry** prev = &results;
ResultEntry* current = results;
ssize_t ret = -1;
while(current) {
if(current->aiocbp == aiocbp) {
*prev = current->next;
ret = current->result;
free(current);
break;
}
prev = &current->next;
current = current->next;
}
pthread_mutex_unlock(&result_mutex);
return ret;
}
DLSYM_WRAPPER(ssize_t, aio_return, (struct aiocb * aiocbp), (aiocbp),
"aio_return")
ssize_t
aio_return(struct aiocb* aiocbp) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(aiocbp->aio_fildes)) {
ssize_t ret = get_result(aiocbp);
DEBUG_INFO("AIO_RETURN {} -> {}", aiocbp->aio_fildes, ret);
if(ret == -1)
errno = EINVAL;
return ret;
}
GKFS_FALLBACK(aio_return, aiocbp)
}
DLSYM_WRAPPER(int, aio_error, (const struct aiocb* aiocbp), (aiocbp),
"aio_error")
int
aio_error(const struct aiocb* aiocbp) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(aiocbp->aio_fildes)) {
DEBUG_INFO("AIO_ERROR {}", aiocbp->aio_fildes);
return 0;
}
GKFS_FALLBACK(aio_error, aiocbp)
}
DLSYM_WRAPPER(char*, getcwd, (char* buffer, size_t size), (buffer, size),
"getcwd")
char*
getcwd(char* buffer, size_t size) {
initializeGekko();
if(CTX->interception_enabled()) {
if(buffer == nullptr) {
if(size != 0)
buffer = (char*) malloc(size);
else {
// allocate buffer big enough
buffer = (char*) malloc(CTX->cwd().size() + 1);
size = CTX->cwd().size() + 1;
}
}
if(CTX->cwd().size() + 1 > size) {
errno = ERANGE;
return NULL;
}
DEBUG_INFO("[GKFS] Size: {} / CWD {}", size, CTX->cwd().c_str());
strcpy(buffer, CTX->cwd().c_str());
return buffer;
}
GKFS_FALLBACK(getcwd, buffer, size);
}
DLSYM_WRAPPER(void, rewinddir, (DIR * dirstream), (dirstream), "rewinddir")
void
rewinddir(DIR* dirstream) {
initializeGekko();
if(CTX->interception_enabled() && is_gkfs_fd(dirstream->fd)) {
DEBUG_INFO("[GKFS] {}", dirstream->fd);
auto open_dir = CTX->file_map()->get_dir(dirstream->fd);
if(open_dir == nullptr) {
// Cast did not succeeded: open_file is a regular file
errno = EBADF;
return;
}
open_dir->pos(0);
return;
}
GKFS_FALLBACK(rewinddir, dirstream)
}
int
renameat(int olddirfd, const char* oldpath, int newdirfd, const char* newpath) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(olddirfd, oldpath, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
std::string resolved2;
if(resolve_gkfs_path(newdirfd, newpath, resolved2) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved2);
#ifdef HAS_RENAME
return gkfs::syscall::gkfs_rename(resolved, resolved2);
#else
errno = ENOTSUP;
return -1;
#endif
}
}
}
GKFS_FALLBACK(renameat, olddirfd, oldpath, newdirfd, newpath);
}
int
renameat2(int olddirfd, const char* oldpath, int newdirfd, const char* newpath,
unsigned int flags) {
initializeGekko();
if(CTX->interception_enabled()) {
std::string resolved;
if(resolve_gkfs_path(olddirfd, oldpath, resolved) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved);
std::string resolved2;
if(resolve_gkfs_path(newdirfd, newpath, resolved2) ==
PathStatus::Internal) {
DEBUG_INFO("[GKFS] {}", resolved2);
#ifdef HAS_RENAME
return gkfs::syscall::gkfs_rename(resolved, resolved2);
#else
errno = ENOTSUP;
return -1;
#endif
}
}
}
GKFS_FALLBACK(renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
}
...@@ -134,11 +134,12 @@ hook_stat(const char* path, struct stat* buf) { ...@@ -134,11 +134,12 @@ hook_stat(const char* path, struct stat* buf) {
int int
hook_statx(int dirfd, const char* path, int flags, unsigned int mask, hook_statx(int dirfd, const char* path, int flags, unsigned int mask,
struct ::statx* buf) { struct ::statx* buf) {
bool follow = (flags & AT_SYMLINK_NOFOLLOW) == 0;
LOG(DEBUG, LOG(DEBUG,
"{}() called with dirfd: '{}', path: \"{}\", flags: '{}', mask: '{}', buf: '{}'", "{}() called with dirfd: '{}', path: \"{}\", flags: '{}', mask: '{}', buf: '{}'",
__func__, dirfd, path, flags, mask, fmt::ptr(buf)); __func__, dirfd, path, flags, mask, fmt::ptr(buf));
std::string resolved; std::string resolved;
auto rstatus = CTX->relativize_fd_path(dirfd, path, resolved); auto rstatus = CTX->relativize_fd_path(dirfd, path, resolved);
switch(rstatus) { switch(rstatus) {
...@@ -154,8 +155,8 @@ hook_statx(int dirfd, const char* path, int flags, unsigned int mask, ...@@ -154,8 +155,8 @@ hook_statx(int dirfd, const char* path, int flags, unsigned int mask,
return -ENOTDIR; return -ENOTDIR;
case gkfs::preload::RelativizeStatus::internal: case gkfs::preload::RelativizeStatus::internal:
return with_errno(gkfs::syscall::gkfs_statx(dirfd, resolved.c_str(), return with_errno(gkfs::syscall::gkfs_statx(
flags, mask, buf)); dirfd, resolved.c_str(), flags, mask, buf, follow));
default: default:
LOG(ERROR, "{}() relativize status unknown: {}", __func__); LOG(ERROR, "{}() relativize status unknown: {}", __func__);
...@@ -174,7 +175,7 @@ hook_lstat(const char* path, struct stat* buf) { ...@@ -174,7 +175,7 @@ hook_lstat(const char* path, struct stat* buf) {
std::string rel_path; std::string rel_path;
if(CTX->relativize_path(path, rel_path)) { if(CTX->relativize_path(path, rel_path)) {
return with_errno(gkfs::syscall::gkfs_stat(rel_path, buf)); return with_errno(gkfs::syscall::gkfs_stat(rel_path, buf, false));
} }
return gsl::narrow_cast<int>( return gsl::narrow_cast<int>(
syscall_no_intercept_wrapper(SYS_lstat, rel_path.c_str(), buf)); syscall_no_intercept_wrapper(SYS_lstat, rel_path.c_str(), buf));
...@@ -193,7 +194,7 @@ hook_fstat(unsigned int fd, struct stat* buf) { ...@@ -193,7 +194,7 @@ hook_fstat(unsigned int fd, struct stat* buf) {
// We can change file_map and recall // We can change file_map and recall
auto md = gkfs::utils::get_metadata(path, false); auto md = gkfs::utils::get_metadata(path, false);
if(md.has_value() && md.value().blocks() == -1) { if(md.has_value() && md.value().blocks() == -1) {
path = md.value().rename_path(); path = md.value().target_path();
} }
#endif #endif
return with_errno(gkfs::syscall::gkfs_stat(path, buf)); return with_errno(gkfs::syscall::gkfs_stat(path, buf));
...@@ -204,10 +205,11 @@ hook_fstat(unsigned int fd, struct stat* buf) { ...@@ -204,10 +205,11 @@ hook_fstat(unsigned int fd, struct stat* buf) {
int int
hook_fstatat(int dirfd, const char* cpath, struct stat* buf, int flags) { hook_fstatat(int dirfd, const char* cpath, struct stat* buf, int flags) {
bool follow = (flags & AT_SYMLINK_NOFOLLOW) == 0;
LOG(DEBUG, "{}() called with path: \"{}\", fd: {}, buf: {}, flags: {}", LOG(DEBUG, "{}() called with path: \"{}\", fd: {}, buf: {}, flags: {}",
__func__, cpath, dirfd, fmt::ptr(buf), flags); __func__, cpath, dirfd, fmt::ptr(buf), flags);
std::string resolved; std::string resolved;
auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, flags); auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, flags);
switch(rstatus) { switch(rstatus) {
...@@ -223,7 +225,7 @@ hook_fstatat(int dirfd, const char* cpath, struct stat* buf, int flags) { ...@@ -223,7 +225,7 @@ hook_fstatat(int dirfd, const char* cpath, struct stat* buf, int flags) {
return -ENOTDIR; return -ENOTDIR;
case gkfs::preload::RelativizeStatus::internal: case gkfs::preload::RelativizeStatus::internal:
return with_errno(gkfs::syscall::gkfs_stat(resolved, buf)); return with_errno(gkfs::syscall::gkfs_stat(resolved, buf, follow));
default: default:
LOG(ERROR, "{}() relativize status unknown: {}", __func__); LOG(ERROR, "{}() relativize status unknown: {}", __func__);
...@@ -383,10 +385,17 @@ hook_symlinkat(const char* oldname, int newdfd, const char* newname) { ...@@ -383,10 +385,17 @@ hook_symlinkat(const char* oldname, int newdfd, const char* newname) {
LOG(DEBUG, "{}() called with oldname: \"{}\", newfd: {}, newname: \"{}\"", LOG(DEBUG, "{}() called with oldname: \"{}\", newfd: {}, newname: \"{}\"",
__func__, oldname, newdfd, newname); __func__, oldname, newdfd, newname);
#ifdef HAS_SYMLINKS
bool internal1 = false;
#endif
std::string oldname_resolved; std::string oldname_resolved;
if(CTX->relativize_path(oldname, oldname_resolved)) { if(CTX->relativize_path(oldname, oldname_resolved)) {
#ifdef HAS_SYMLINKS
internal1 = true;
#else
LOG(WARNING, "{}() operation not supported", __func__); LOG(WARNING, "{}() operation not supported", __func__);
return -ENOTSUP; return -ENOTSUP;
#endif
} }
std::string newname_resolved; std::string newname_resolved;
...@@ -405,8 +414,17 @@ hook_symlinkat(const char* oldname, int newdfd, const char* newname) { ...@@ -405,8 +414,17 @@ hook_symlinkat(const char* oldname, int newdfd, const char* newname) {
return -ENOTDIR; return -ENOTDIR;
case gkfs::preload::RelativizeStatus::internal: case gkfs::preload::RelativizeStatus::internal:
#ifdef HAS_SYMLINKS
if(internal1) { // Parameters are inverted
return with_errno(gkfs::syscall::gkfs_mk_symlink(
newname_resolved, oldname_resolved));
}
LOG(WARNING, "{}() operation not supported", __func__);
return -ENOTSUP;
#else
LOG(WARNING, "{}() operation not supported", __func__); LOG(WARNING, "{}() operation not supported", __func__);
return -ENOTSUP; return -ENOTSUP;
#endif
default: default:
LOG(ERROR, "{}() relativize status unknown", __func__); LOG(ERROR, "{}() relativize status unknown", __func__);
...@@ -704,7 +722,8 @@ hook_chdir(const char* path) { ...@@ -704,7 +722,8 @@ hook_chdir(const char* path) {
// path falls in our namespace // path falls in our namespace
auto md = gkfs::utils::get_metadata(rel_path); auto md = gkfs::utils::get_metadata(rel_path);
if(!md) { if(!md) {
LOG(ERROR, "{}() path {} errno {}", __func__, path, errno); LOG(ERROR, "{}() path {} / {} errno {}", __func__, path, rel_path,
errno);
return -errno; return -errno;
} }
...@@ -802,6 +821,11 @@ hook_readlinkat(int dirfd, const char* cpath, char* buf, int bufsiz) { ...@@ -802,6 +821,11 @@ hook_readlinkat(int dirfd, const char* cpath, char* buf, int bufsiz) {
return -ENOTDIR; return -ENOTDIR;
case gkfs::preload::RelativizeStatus::internal: case gkfs::preload::RelativizeStatus::internal:
#ifdef HAS_SYMLINKS
return with_errno(
gkfs::syscall::gkfs_readlink(resolved, buf, bufsiz));
#endif
return -EINVAL; return -EINVAL;
default: default:
...@@ -861,22 +885,78 @@ hook_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) { ...@@ -861,22 +885,78 @@ hook_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) {
ret |= O_RDWR; ret |= O_RDWR;
} }
return ret; return ret;
case F_SETFL:
LOG(DEBUG, "{}() F_SETFL on fd {}", __func__, fd);
// get flags from arg and setup
if(arg & O_RDONLY) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::rdonly, true);
}
if(arg & O_WRONLY) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::wronly, true);
}
if(arg & O_RDWR) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::rdwr, true);
}
if(arg & O_APPEND) {
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::append, true);
}
if(arg & O_NONBLOCK) {
LOG(DEBUG, "[GKFS] F_SETFL {} NONBLOCK", fd);
}
if(arg & O_ASYNC) {
LOG(DEBUG, "[GKFS] F_SETFL {} ASYNC", fd);
}
if(arg & O_CLOEXEC) {
LOG(DEBUG, "[GKFS] F_SETFL {} CLOEXEC", fd);
CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::cloexec, true);
}
return 0;
case F_SETFD: case F_SETFD:
LOG(DEBUG, "{}() [fd: {}, cmd: F_SETFD, FD_CLOEXEC: {}]", __func__, LOG(DEBUG, "{}() [fd: {}, cmd: F_SETFD, FD_CLOEXEC: {}]", __func__,
fd, (arg & FD_CLOEXEC)); fd, (arg & FD_CLOEXEC));
CTX->file_map()->get(fd)->set_flag( CTX->file_map()->get(fd)->set_flag(
gkfs::filemap::OpenFile_flags::cloexec, (arg & FD_CLOEXEC)); gkfs::filemap::OpenFile_flags::cloexec, (arg & FD_CLOEXEC));
return 0; return 0;
#ifdef ENABLE_LOCKING
case F_GETLK: case F_GETLK:
LOG(ERROR, "{}() F_GETLK on fd (Not Supported) {}", __func__, fd); LOG(ERROR, "{}() F_GETLK on fd (on underlying fd) {}", __func__,
return 0; fd);
return gsl::narrow_cast<int>(
syscall_no_intercept_wrapper(SYS_fcntl, fd, cmd, arg));
case F_SETLK: case F_SETLK:
LOG(ERROR, "{}() F_SETLK on fd (Not Supported) {}", __func__, fd); LOG(ERROR, "{}() F_SETLK on fd (on underlying fd) {}", __func__,
return 0; fd);
return gsl::narrow_cast<int>(
syscall_no_intercept_wrapper(SYS_fcntl, fd, cmd, arg));
case F_SETLKW:
LOG(ERROR, "{}() F_SETLKW on fd (on underlying fd) {}", __func__,
fd);
return gsl::narrow_cast<int>(
syscall_no_intercept_wrapper(SYS_fcntl, fd, cmd, arg));
case F_GETOWN:
case __F_GETOWN_EX:
LOG(ERROR, "{}() F_GETOWN on fd (on underlying fd) {}", __func__,
fd);
return gsl::narrow_cast<int>(
syscall_no_intercept_wrapper(SYS_fcntl, fd, cmd, arg));
case F_SETOWN:
case __F_SETOWN_EX:
LOG(ERROR, "{}() F_SETOWN on fd (on underlying fd) {}", __func__,
fd);
return gsl::narrow_cast<int>(
syscall_no_intercept_wrapper(SYS_fcntl, fd, cmd, arg));
#endif
default: default:
LOG(ERROR, "{}() unrecognized command {} on fd {}", __func__, cmd, LOG(ERROR, "{}() unrecognized command {} on fd {}", __func__, cmd,
fd); fd);
......
...@@ -92,6 +92,62 @@ get_current_syscall_info() { ...@@ -92,6 +92,62 @@ get_current_syscall_info() {
return saved_syscall_info; return saved_syscall_info;
} }
std::vector<int>
get_open_fds() {
std::vector<int> fds;
const int buffer_size = 4096;
char buffer[buffer_size];
// Open /proc/self/fd directory using raw syscall
int dir_fd = syscall_no_intercept_wrapper(SYS_open, "/proc/self/fd",
O_RDONLY | O_DIRECTORY, 0);
if(dir_fd < 0) {
return fds;
}
while(true) {
// Read directory entries using getdents64 syscall
int nread = syscall_no_intercept_wrapper(SYS_getdents64, dir_fd, buffer,
buffer_size);
if(nread <= 0)
break;
for(int bpos = 0; bpos < nread;) {
auto* dent = reinterpret_cast<linux_dirent64*>(buffer + bpos);
// Skip . and .. entries
const std::string d_name(dent->d_name);
if(d_name == "." || d_name == "..") {
bpos += dent->d_reclen;
continue;
}
try {
int fd = std::stoi(d_name);
// Skip the directory FD itself
if(fd != dir_fd) {
fds.push_back(fd);
}
} catch(const std::exception&) {
// Ignore non-integer entries
}
bpos += dent->d_reclen;
}
}
// Close directory using raw syscall
syscall_no_intercept_wrapper(SYS_close, dir_fd);
// Filter out non-open FDs (optional safety check)
fds.erase(std::remove_if(fds.begin(), fds.end(),
[](int fd) {
return syscall(SYS_fcntl, fd, F_GETFD) < 0;
}),
fds.end());
return fds;
}
/* /*
* hook_internal -- interception hook for internal syscalls * hook_internal -- interception hook for internal syscalls
...@@ -512,15 +568,18 @@ hook(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, ...@@ -512,15 +568,18 @@ hook(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4,
*result = gkfs::hook::hook_close(static_cast<int>(arg0)); *result = gkfs::hook::hook_close(static_cast<int>(arg0));
break; break;
#ifdef SYS_close_range #ifdef SYS_close_range
case SYS_close_range: case SYS_close_range: {
for(auto i = arg0; i <= arg1; i++) { auto fds = get_open_fds();
if(i >= GKFS_MAX_OPEN_FDS) for(auto fd : fds) {
break; if(fd < static_cast<int>(arg0) || fd > static_cast<int>(arg1))
if(CTX->file_map()->exist(i)) { continue;
gkfs::syscall::gkfs_close(i); if(CTX->file_map()->exist(fd)) {
} gkfs::syscall::gkfs_close(fd);
} else
close(fd);
*result = 0; *result = 0;
} }
}
*result = 0; *result = 0;
break; break;
#endif // SYS_close_range #endif // SYS_close_range
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
extern "C" { extern "C" {
#include <sys/stat.h> #include <sys/stat.h>
#include <dlfcn.h>
} }
using namespace std; using namespace std;
...@@ -111,16 +112,25 @@ match_components(const string& path, unsigned int& path_components, ...@@ -111,16 +112,25 @@ match_components(const string& path, unsigned int& path_components,
return matched; return matched;
} }
static char* (*real_realpath)(const char* path, char* resolved_path) = nullptr;
string string
follow_symlinks(const string& path) { follow_symlinks(const string& path) {
struct stat st{}; struct stat st{};
if(lstat(path.c_str(), &st) < 0) { auto res = syscall_no_intercept(SYS_lstat, path.c_str(), &st);
if(res < 0) {
LOG(DEBUG, "path \"{}\" does not exist", path); LOG(DEBUG, "path \"{}\" does not exist", path);
return path; return path;
} }
if(S_ISLNK(st.st_mode)) { if(S_ISLNK(st.st_mode)) {
auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]); auto link_resolved = ::unique_ptr<char[]>(new char[PATH_MAX]);
if(realpath(path.c_str(), link_resolved.get()) == nullptr) { if(real_realpath == nullptr) {
real_realpath = reinterpret_cast<char* (*) (const char* path,
char* resolved_path)>(
dlsym(((void*) -1l), "realpath"));
}
if(real_realpath(path.c_str(), link_resolved.get()) == nullptr) {
LOG(ERROR, LOG(ERROR,
"Failed to get realpath for link \"{}\". " "Failed to get realpath for link \"{}\". "
...@@ -198,7 +208,6 @@ resolve_new(const string& path, bool resolve_last_link) { ...@@ -198,7 +208,6 @@ resolve_new(const string& path, bool resolve_last_link) {
} }
#endif #endif
} }
if(resolved.substr(0, CTX->mountdir().size()) == CTX->mountdir()) { if(resolved.substr(0, CTX->mountdir().size()) == CTX->mountdir()) {
resolved.erase(1, CTX->mountdir().size()); resolved.erase(1, CTX->mountdir().size());
LOG(DEBUG, "internal: \"{}\"", resolved); LOG(DEBUG, "internal: \"{}\"", resolved);
......
...@@ -239,6 +239,16 @@ init_environment() { ...@@ -239,6 +239,16 @@ init_environment() {
"Failed to connect to hosts: "s + e.what()); "Failed to connect to hosts: "s + e.what());
} }
LOG(INFO, "Lock-Files : Generator = {} / Consumer = {}",
(bool) gkfs::env::get_var(gkfs::env::PROTECT_FILES_GENERATOR, 0),
(bool) gkfs::env::get_var(gkfs::env::PROTECT_FILES_CONSUMER, 0));
CTX->protect_files_generator(
gkfs::env::get_var(gkfs::env::PROTECT_FILES_GENERATOR, 0));
CTX->protect_files_consumer(
gkfs::env::get_var(gkfs::env::PROTECT_FILES_CONSUMER, 0));
/* Setup distributor */ /* Setup distributor */
auto forwarding_map_file = gkfs::env::get_var( auto forwarding_map_file = gkfs::env::get_var(
gkfs::env::FORWARDING_MAP_FILE, gkfs::config::forwarding_file_path); gkfs::env::FORWARDING_MAP_FILE, gkfs::config::forwarding_file_path);
...@@ -329,13 +339,14 @@ init_environment() { ...@@ -329,13 +339,14 @@ init_environment() {
} }
} }
LOG(INFO, "Retrieving file system configuration..."); // LOG(INFO, "Retrieving file system configuration...");
if(!gkfs::rpc::forward_get_fs_config()) { // if(!gkfs::rpc::forward_get_fs_config()) {
exit_error_msg( // exit_error_msg(
EXIT_FAILURE, // EXIT_FAILURE,
"Unable to fetch file system configurations from daemon process through RPC."); // "Unable to fetch file system configurations from daemon
} // process through RPC.");
// }
// Initialize random number generator and seed for replica selection // Initialize random number generator and seed for replica selection
// in case of failure, a new replica will be selected // in case of failure, a new replica will be selected
if(CTX->get_replicas() > 0) { if(CTX->get_replicas() > 0) {
...@@ -359,14 +370,12 @@ init_preload() { ...@@ -359,14 +370,12 @@ init_preload() {
// The original errno value will be restored after initialization to not // The original errno value will be restored after initialization to not
// leak internal error codes // leak internal error codes
auto oerrno = errno; auto oerrno = errno;
if(atomic_exchange(&init, 1) == 0) {
pthread_atfork(&at_fork_syscall, &at_parent_syscall, &at_child_syscall);
}
CTX->enable_interception(); CTX->enable_interception();
gkfs::preload::start_self_interception(); gkfs::preload::start_self_interception();
if(!init) {
init = true;
pthread_atfork(&at_fork_syscall, &at_parent_syscall, &at_child_syscall);
}
CTX->init_logging(); CTX->init_logging();
// from here ownwards it is safe to print messages // from here ownwards it is safe to print messages
LOG(DEBUG, "Logging subsystem initialized"); LOG(DEBUG, "Logging subsystem initialized");
...@@ -384,9 +393,6 @@ init_preload() { ...@@ -384,9 +393,6 @@ init_preload() {
if(gkfs::env::var_is_set(gkfs::env::PROTECT_FD)) { if(gkfs::env::var_is_set(gkfs::env::PROTECT_FD)) {
CTX->protect_fds(true); CTX->protect_fds(true);
LOG(INFO, "Protecting user fds"); LOG(INFO, "Protecting user fds");
} else {
CTX->protect_fds(false);
LOG(INFO, "Not protecting user fds");
} }
if(CTX->protect_fds()) { if(CTX->protect_fds()) {
...@@ -466,12 +472,22 @@ destroy_preload() { ...@@ -466,12 +472,22 @@ destroy_preload() {
extern "C" int extern "C" int
gkfs_init() { gkfs_init() {
CTX->init_logging(); CTX->init_logging();
// from here ownwards it is safe to print messages // from here ownwards it is safe to print messages
LOG(DEBUG, "Logging subsystem initialized"); LOG(DEBUG, "Logging subsystem initialized");
if(gkfs::env::var_is_set(gkfs::env::PROTECT_FD)) {
CTX->protect_fds(true);
LOG(INFO, "Protecting user fds");
}
if(CTX->protect_fds()) {
CTX->protect_user_fds();
}
gkfs::preload::init_environment(); gkfs::preload::init_environment();
if(CTX->protect_fds())
CTX->unprotect_user_fds();
return 0; return 0;
} }
...@@ -489,6 +505,76 @@ gkfs_end() { ...@@ -489,6 +505,76 @@ gkfs_end() {
return 0; return 0;
} }
/**
* @brief Automatically launch init/destroy
* NOTES: this is not called, in the child of a fork
*/
void
init_libc() {
CTX->init_logging();
// from here ownwards it is safe to print messages
LOG(DEBUG, "Logging subsystem initialized");
if(gkfs::env::var_is_set(gkfs::env::PROTECT_FD)) {
CTX->protect_fds(true);
LOG(INFO, "Protecting user fds");
}
if(CTX->protect_fds()) {
CTX->protect_user_fds();
}
if(atomic_exchange(&init, 1) == 0) {
pthread_atfork(&at_fork, &at_parent, &at_child);
}
gkfs::preload::init_environment();
if(CTX->protect_fds()) {
CTX->unprotect_user_fds();
}
CTX->enable_interception();
if(!CTX->init_metrics()) {
exit_error_msg(EXIT_FAILURE,
"Unable to initialize client metrics. Exiting...");
}
}
void
destroy_libc() {
CTX->disable_interception();
#ifdef GKFS_ENABLE_CLIENT_METRICS
LOG(INFO, "Flushing final metrics...");
CTX->write_metrics()->flush_msgpack();
CTX->read_metrics()->flush_msgpack();
LOG(INFO, "Metrics flushed. Total flush operations: {}",
CTX->write_metrics()->flush_count());
CTX->destroy_metrics();
#endif
CTX->clear_hosts();
LOG(DEBUG, "Peer information deleted");
ld_network_service.reset();
LOG(DEBUG, "RPC subsystem shut down");
LOG(INFO, "All subsystems shut down. Client shutdown complete.");
}
void
at_fork() {
destroy_libc();
}
void
at_parent() {
init_libc();
}
void
at_child() {
init_libc();
}
void void
at_fork_syscall() { at_fork_syscall() {
destroy_preload(); destroy_preload();
......
...@@ -85,6 +85,7 @@ PreloadContext::PreloadContext() ...@@ -85,6 +85,7 @@ PreloadContext::PreloadContext()
char host[255]; char host[255];
gethostname(host, 255); gethostname(host, 255);
hostname = host; hostname = host;
cwd_ = gkfs::path::get_sys_cwd();
PreloadContext::set_replicas( PreloadContext::set_replicas(
std::stoi(gkfs::env::get_var(gkfs::env::NUM_REPL, "0"))); std::stoi(gkfs::env::get_var(gkfs::env::NUM_REPL, "0")));
} }
...@@ -659,6 +660,28 @@ PreloadContext::get_replicas() { ...@@ -659,6 +660,28 @@ PreloadContext::get_replicas() {
return replicas_; return replicas_;
} }
bool
PreloadContext::protect_files_generator() const {
return protect_files_generator_;
}
void
PreloadContext::protect_files_generator(bool protect) {
protect_files_generator_ = protect;
}
bool
PreloadContext::protect_files_consumer() const {
return protect_files_consumer_;
}
void
PreloadContext::protect_files_consumer(bool protect) {
protect_files_consumer_ = protect;
}
const std::shared_ptr<messagepack::ClientMetrics> const std::shared_ptr<messagepack::ClientMetrics>
PreloadContext::write_metrics() { PreloadContext::write_metrics() {
return write_metrics_; return write_metrics_;
......
...@@ -166,8 +166,10 @@ load_hostfile(const std::string& path) { ...@@ -166,8 +166,10 @@ load_hostfile(const std::string& path) {
path, strerror(errno))); path, strerror(errno)));
} }
vector<pair<string, string>> hosts; vector<pair<string, string>> hosts;
const regex line_re("^(\\S+)\\s+(\\S+)\\s*(\\S*)$", const regex line_re(
"^(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)$",
regex::ECMAScript | regex::optimize); regex::ECMAScript | regex::optimize);
string line; string line;
string host; string host;
string uri; string uri;
...@@ -185,9 +187,25 @@ load_hostfile(const std::string& path) { ...@@ -185,9 +187,25 @@ load_hostfile(const std::string& path) {
throw runtime_error( throw runtime_error(
fmt::format("unrecognized line format: '{}'", line)); fmt::format("unrecognized line format: '{}'", line));
} }
host = match[1]; host = match[1];
uri = match[2]; uri = match[2];
// match[3] that is the proxy (not used here)
hosts.emplace_back(host, uri); hosts.emplace_back(host, uri);
// info will be repeated line per line:
CTX->mountdir(match[4]);
LOG(INFO, "Mountdir: '{}'", CTX->mountdir());
CTX->fs_conf()->rootdir = match[5];
CTX->fs_conf()->atime_state = match[6] == '1';
CTX->fs_conf()->mtime_state = match[7] == '1';
CTX->fs_conf()->ctime_state = match[8] == '1';
CTX->fs_conf()->link_cnt_state = match[9] == '1';
CTX->fs_conf()->blocks_state = match[10] == '1';
// convert match[11] and match[12] to unsigned integers.
CTX->fs_conf()->uid = std::stoi(match[11]);
CTX->fs_conf()->gid = std::stoi(match[12]);
} }
if(hosts.empty()) { if(hosts.empty()) {
throw runtime_error( throw runtime_error(
...@@ -264,6 +282,7 @@ get_metadata(const string& path, bool follow_links) { ...@@ -264,6 +282,7 @@ get_metadata(const string& path, bool follow_links) {
#ifdef HAS_SYMLINKS #ifdef HAS_SYMLINKS
if(follow_links) { if(follow_links) {
gkfs::metadata::Metadata md{attr}; gkfs::metadata::Metadata md{attr};
auto original = md;
while(md.is_link()) { while(md.is_link()) {
if(gkfs::config::proxy::fwd_stat && CTX->use_proxy()) { if(gkfs::config::proxy::fwd_stat && CTX->use_proxy()) {
err = gkfs::rpc::forward_stat_proxy(md.target_path(), attr); err = gkfs::rpc::forward_stat_proxy(md.target_path(), attr);
......
...@@ -406,9 +406,34 @@ int ...@@ -406,9 +406,34 @@ int
forward_rename(const string& oldpath, const string& newpath, forward_rename(const string& oldpath, const string& newpath,
const gkfs::metadata::Metadata& md) { const gkfs::metadata::Metadata& md) {
auto endp = CTX->hosts().at( auto endp = CTX->hosts().at(
CTX->distributor()->locate_file_metadata(oldpath, 0)); CTX->distributor()->locate_file_metadata(oldpath, 0));
if(newpath == "") { // Just cleanup rename status
try {
LOG(DEBUG, "Sending RPC ...");
// TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that
// we can retry for RPC_TRIES (see old commits with margo)
// TODO(amiranda): hermes will eventually provide a post(endpoint)
// returning one result and a broadcast(endpoint_set) returning a
// result_set. When that happens we can remove the .at(0) :/
auto out = ld_network_service
->post<gkfs::rpc::rename>(endp, oldpath, newpath)
.get()
.at(0);
LOG(DEBUG, "Got response success: {}", out.err());
// return out.err() ? out.err() : 0;
return 0;
} catch(const std::exception& ex) {
LOG(ERROR, "while getting rpc output");
return EBUSY;
}
}
try { try {
LOG(DEBUG, "Sending RPC ..."); LOG(DEBUG, "Sending RPC ...");
// TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we // TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we
...@@ -484,9 +509,8 @@ forward_rename(const string& oldpath, const string& newpath, ...@@ -484,9 +509,8 @@ forward_rename(const string& oldpath, const string& newpath,
// returning one result and a broadcast(endpoint_set) returning a // returning one result and a broadcast(endpoint_set) returning a
// result_set. When that happens we can remove the .at(0) :/ // result_set. When that happens we can remove the .at(0) :/
// Update new file with target link = oldpath // Update new file with target link = oldpath
auto out = auto out = ld_network_service
ld_network_service ->post<gkfs::rpc::rename>(endp2, newpath, oldpath)
->post<gkfs::rpc::mk_symlink>(endp2, newpath, oldpath)
.get() .get()
.at(0); .at(0);
...@@ -508,7 +532,7 @@ forward_rename(const string& oldpath, const string& newpath, ...@@ -508,7 +532,7 @@ forward_rename(const string& oldpath, const string& newpath,
// returning one result and a broadcast(endpoint_set) returning a // returning one result and a broadcast(endpoint_set) returning a
// result_set. When that happens we can remove the .at(0) :/ // result_set. When that happens we can remove the .at(0) :/
auto out = ld_network_service auto out = ld_network_service
->post<gkfs::rpc::mk_symlink>(endp, oldpath, newpath) ->post<gkfs::rpc::rename>(endp, oldpath, newpath)
.get() .get()
.at(0); .at(0);
......
...@@ -62,6 +62,9 @@ hermes::detail::register_user_request_types(uint32_t provider_id) { ...@@ -62,6 +62,9 @@ hermes::detail::register_user_request_types(uint32_t provider_id) {
#ifdef HAS_SYMLINKS #ifdef HAS_SYMLINKS
(void) registered_requests().add<gkfs::rpc::mk_symlink>(provider_id); (void) registered_requests().add<gkfs::rpc::mk_symlink>(provider_id);
#endif // HAS_SYMLINKS #endif // HAS_SYMLINKS
#ifdef HAS_RENAME
(void) registered_requests().add<gkfs::rpc::rename>(provider_id);
#endif // HAS_RENAME
(void) registered_requests().add<gkfs::rpc::remove_data>(provider_id); (void) registered_requests().add<gkfs::rpc::remove_data>(provider_id);
(void) registered_requests().add<gkfs::rpc::write_data>(provider_id); (void) registered_requests().add<gkfs::rpc::write_data>(provider_id);
(void) registered_requests().add<gkfs::rpc::read_data>(provider_id); (void) registered_requests().add<gkfs::rpc::read_data>(provider_id);
......
...@@ -163,9 +163,10 @@ Stats::output_map(std::ofstream& output) { ...@@ -163,9 +163,10 @@ Stats::output_map(std::ofstream& output) {
} }
auto chunkMap = auto chunkMap =
[](std::string caption, [](const std::string& caption,
map<unsigned int, const map<unsigned int,
std::set<pair<std::string, unsigned long long>>>& order, std::set<pair<std::string, unsigned long long>>>&
order,
std::ofstream& output) { std::ofstream& output) {
output << caption << std::endl; output << caption << std::endl;
for(auto k : order) { for(auto k : order) {
......
...@@ -178,6 +178,10 @@ register_server_rpcs(margo_instance_id mid) { ...@@ -178,6 +178,10 @@ register_server_rpcs(margo_instance_id mid) {
#ifdef HAS_SYMLINKS #ifdef HAS_SYMLINKS
MARGO_REGISTER(mid, gkfs::rpc::tag::mk_symlink, rpc_mk_symlink_in_t, MARGO_REGISTER(mid, gkfs::rpc::tag::mk_symlink, rpc_mk_symlink_in_t,
rpc_err_out_t, rpc_srv_mk_symlink); rpc_err_out_t, rpc_srv_mk_symlink);
#endif
#ifdef HAS_RENAME
MARGO_REGISTER(mid, gkfs::rpc::tag::rename, rpc_rename_in_t, rpc_err_out_t,
rpc_srv_rename);
#endif #endif
MARGO_REGISTER(mid, gkfs::rpc::tag::write, rpc_write_data_in_t, MARGO_REGISTER(mid, gkfs::rpc::tag::write, rpc_write_data_in_t,
rpc_data_out_t, rpc_srv_write); rpc_data_out_t, rpc_srv_write);
...@@ -584,7 +588,7 @@ agios_initialize() { ...@@ -584,7 +588,7 @@ agios_initialize() {
agios_exit(); agios_exit();
throw; std::terminate();
} }
} }
#endif #endif
...@@ -885,10 +889,9 @@ parse_input(const cli_options& opts, const CLI::App& desc) { ...@@ -885,10 +889,9 @@ parse_input(const cli_options& opts, const CLI::App& desc) {
metadir_path.native()); metadir_path.native());
} else { } else {
// use rootdir as metadata dir // use rootdir as metadata dir
auto metadir = opts.rootdir;
if(GKFS_DATA->enable_forwarding()) { if(GKFS_DATA->enable_forwarding()) {
auto metadir = opts.rootdir;
// As we store normally he metadata to the pfs, we need to put each // As we store normally he metadata to the pfs, we need to put each
// daemon in a separate directory. // daemon in a separate directory.
auto metadir_path = auto metadir_path =
......
...@@ -847,7 +847,66 @@ rpc_srv_get_dirents_extended(hg_handle_t handle) { ...@@ -847,7 +847,66 @@ rpc_srv_get_dirents_extended(hg_handle_t handle) {
return gkfs::rpc::cleanup_respond(&handle, &in, &out, &bulk_handle); return gkfs::rpc::cleanup_respond(&handle, &in, &out, &bulk_handle);
} }
#if defined(HAS_SYMLINKS) || defined(HAS_RENAME) #if defined(HAS_SYMLINKS)
/**
* @brief Serves a request create a symbolic link
* @internal
* The state of this function is unclear and requires a complete refactor.
*
* All exceptions must be caught here and dealt with accordingly. Any errors are
* placed in the response.
* @endinternal
* @param handle Mercury RPC handle (path is the symbolic link)
* @return Mercury error code to Mercury
*/
hg_return_t
rpc_srv_mk_symlink(hg_handle_t handle) {
rpc_mk_symlink_in_t in{};
rpc_err_out_t out{};
auto ret = margo_get_input(handle, &in);
if(ret != HG_SUCCESS) {
GKFS_DATA->spdlogger()->error(
"{}() Failed to retrieve input from handle", __func__);
}
GKFS_DATA->spdlogger()->debug(
"{}() Got RPC with path '{}' and target path '{}'", __func__,
in.path, in.target_path);
// do update
try {
gkfs::metadata::Metadata md = gkfs::metadata::get(in.path);
md.target_path(in.target_path);
md.mode(S_IFLNK);
md.blocks(0);
GKFS_DATA->spdlogger()->debug(
"{}() Updating path '{}' with metadata '{}'", __func__, in.path,
md.serialize());
gkfs::metadata::update(in.path, md);
out.err = 0;
} catch(const std::exception& e) {
// TODO handle NotFoundException
GKFS_DATA->spdlogger()->error("{}() Failed to update entry", __func__);
out.err = 1;
}
GKFS_DATA->spdlogger()->debug("{}() Sending output err '{}'", __func__,
out.err);
auto hret = margo_respond(handle, &out);
if(hret != HG_SUCCESS) {
GKFS_DATA->spdlogger()->error("{}() Failed to respond", __func__);
}
// Destroy handle when finished
margo_free_input(handle, &in);
margo_destroy(handle);
return HG_SUCCESS;
}
#endif // HAS_SYMLINKS
#if defined(HAS_RENAME)
/** /**
* @brief Serves a request create a symbolic link and supports rename * @brief Serves a request create a symbolic link and supports rename
* @internal * @internal
...@@ -856,11 +915,11 @@ rpc_srv_get_dirents_extended(hg_handle_t handle) { ...@@ -856,11 +915,11 @@ rpc_srv_get_dirents_extended(hg_handle_t handle) {
* All exceptions must be caught here and dealt with accordingly. Any errors are * All exceptions must be caught here and dealt with accordingly. Any errors are
* placed in the response. * placed in the response.
* @endinteral * @endinteral
* @param handle Mercury RPC handle * @param handle Mercury RPC handle (target_path is the symbolic link)
* @return Mercury error code to Mercury * @return Mercury error code to Mercury
*/ */
hg_return_t hg_return_t
rpc_srv_mk_symlink(hg_handle_t handle) { rpc_srv_rename(hg_handle_t handle) {
rpc_mk_symlink_in_t in{}; rpc_mk_symlink_in_t in{};
rpc_err_out_t out{}; rpc_err_out_t out{};
...@@ -875,17 +934,14 @@ rpc_srv_mk_symlink(hg_handle_t handle) { ...@@ -875,17 +934,14 @@ rpc_srv_mk_symlink(hg_handle_t handle) {
// do update // do update
try { try {
gkfs::metadata::Metadata md = gkfs::metadata::get(in.path); gkfs::metadata::Metadata md = gkfs::metadata::get(in.path);
#ifdef HAS_RENAME
if(md.blocks() == -1) {
// We need to fill the rename path as this is an inverse path
// old -> new
md.rename_path(in.target_path);
} else {
#endif // HAS_RENAME
md.target_path(in.target_path); md.target_path(in.target_path);
#ifdef HAS_RENAME // We are reverting the rename so we clean up the target_path
if(strcmp(in.target_path, "") == 0) {
md.blocks(0);
} }
#endif // HAS_RENAME
GKFS_DATA->spdlogger()->debug( GKFS_DATA->spdlogger()->debug(
"{}() Updating path '{}' with metadata '{}'", __func__, in.path, "{}() Updating path '{}' with metadata '{}'", __func__, in.path,
md.serialize()); md.serialize());
...@@ -910,7 +966,7 @@ rpc_srv_mk_symlink(hg_handle_t handle) { ...@@ -910,7 +966,7 @@ rpc_srv_mk_symlink(hg_handle_t handle) {
return HG_SUCCESS; return HG_SUCCESS;
} }
#endif // HAS_SYMLINKS || HAS_RENAME #endif // HAS_RENAME
} // namespace } // namespace
...@@ -938,3 +994,6 @@ DEFINE_MARGO_RPC_HANDLER(rpc_srv_get_dirents_extended) ...@@ -938,3 +994,6 @@ DEFINE_MARGO_RPC_HANDLER(rpc_srv_get_dirents_extended)
DEFINE_MARGO_RPC_HANDLER(rpc_srv_mk_symlink) DEFINE_MARGO_RPC_HANDLER(rpc_srv_mk_symlink)
#endif #endif
#ifdef HAS_RENAME
DEFINE_MARGO_RPC_HANDLER(rpc_srv_rename)
#endif
...@@ -75,8 +75,10 @@ MalleableManager::load_hostfile(const std::string& path) { ...@@ -75,8 +75,10 @@ MalleableManager::load_hostfile(const std::string& path) {
path, strerror(errno))); path, strerror(errno)));
} }
vector<pair<string, string>> hosts; vector<pair<string, string>> hosts;
const regex line_re("^(\\S+)\\s+(\\S+)\\s*(\\S*)$", const regex line_re(
"^(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)$",
regex::ECMAScript | regex::optimize); regex::ECMAScript | regex::optimize);
string line; string line;
string host; string host;
string uri; string uri;
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <thread> #include <thread>
#include <unistd.h>
using namespace std; using namespace std;
...@@ -114,6 +115,16 @@ populate_hosts_file() { ...@@ -114,6 +115,16 @@ populate_hosts_file() {
auto line_out = fmt::format("{} {}", hostname, daemon_addr); auto line_out = fmt::format("{} {}", hostname, daemon_addr);
if(!proxy_addr.empty()) if(!proxy_addr.empty())
line_out = fmt::format("{} {}", line_out, proxy_addr); line_out = fmt::format("{} {}", line_out, proxy_addr);
if(proxy_addr.empty())
line_out = fmt::format("{} {}", line_out, "NOPROXY");
line_out = fmt::format(
"{} {} {} {} {} {} {} {} {} {}", line_out, GKFS_DATA->mountdir(),
GKFS_DATA->rootdir(), (int) GKFS_DATA->atime_state(),
(int) GKFS_DATA->mtime_state(), (int) GKFS_DATA->ctime_state(),
(int) GKFS_DATA->link_cnt_state(), (int) GKFS_DATA->blocks_state(),
getuid(), getgid());
// Constants for retry mechanism // Constants for retry mechanism
const int MAX_RETRIES = 5; // Maximum number of retry attempts const int MAX_RETRIES = 5; // Maximum number of retry attempts
const std::chrono::milliseconds RETRY_DELAY( const std::chrono::milliseconds RETRY_DELAY(
......
...@@ -56,8 +56,10 @@ load_hostfile(const std::string& lfpath) { ...@@ -56,8 +56,10 @@ load_hostfile(const std::string& lfpath) {
lfpath, strerror(errno))); lfpath, strerror(errno)));
} }
vector<pair<string, string>> hosts; vector<pair<string, string>> hosts;
const regex line_re("^(\\S+)\\s+(\\S+)\\s*(\\S*)$", const regex line_re(
"^(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)$",
regex::ECMAScript | regex::optimize); regex::ECMAScript | regex::optimize);
string line; string line;
string host; string host;
string uri; string uri;
...@@ -213,8 +215,8 @@ connect_to_hosts(const vector<pair<string, string>>& hosts) { ...@@ -213,8 +215,8 @@ connect_to_hosts(const vector<pair<string, string>>& hosts) {
ret = margo_addr_lookup(PROXY_DATA->client_rpc_mid(), uri.c_str(), ret = margo_addr_lookup(PROXY_DATA->client_rpc_mid(), uri.c_str(),
&svr_addr); &svr_addr);
if(ret != HG_SUCCESS) { if(ret != HG_SUCCESS) {
// still not working after 5 tries. // still not working after 4 tries.
if(i == 4) { if(i == 3) {
auto err_msg = auto err_msg =
fmt::format("{}() Unable to lookup address '{}'", fmt::format("{}() Unable to lookup address '{}'",
__func__, uri); __func__, uri);
......
...@@ -57,7 +57,7 @@ main(int argc, char* argv[]) { ...@@ -57,7 +57,7 @@ main(int argc, char* argv[]) {
const std::string link_ext = dir_ext + "/tmp/link"; const std::string link_ext = dir_ext + "/tmp/link";
char buffIn[] = "oops."; char buffIn[] = "oops.";
char* buffOut = new char[strlen(buffIn) + 1]; char buffOut[strlen(buffIn) + 1];
struct stat st; struct stat st;
int ret; int ret;
...@@ -170,7 +170,7 @@ main(int argc, char* argv[]) { ...@@ -170,7 +170,7 @@ main(int argc, char* argv[]) {
// Check readlink // Check readlink
char* target_path = new char[target_int.size() + 1]; char target_path[target_int.size() + 1];
ret = readlink(link_int.c_str(), target_path, target_int.size() + 1); ret = readlink(link_int.c_str(), target_path, target_int.size() + 1);
if(ret <= 0) { if(ret <= 0) {
std::cerr << "ERROR: Failed to retrieve link path: " << strerror(errno) std::cerr << "ERROR: Failed to retrieve link path: " << strerror(errno)
......
...@@ -61,7 +61,7 @@ main(int argc, char* argv[]) { ...@@ -61,7 +61,7 @@ main(int argc, char* argv[]) {
string mountdir = "/tmp/mountdir"; string mountdir = "/tmp/mountdir";
string p = mountdir + "/file"; string p = mountdir + "/file";
char buffIn[] = "oops."; char buffIn[] = "oops.";
char* buffOut = new char[strlen(buffIn) + 1 + 20]; char buffOut[strlen(buffIn) + 1 + 20];
int fd; int fd;
int ret; int ret;
struct stat st; struct stat st;
......
...@@ -34,7 +34,7 @@ from pathlib import Path ...@@ -34,7 +34,7 @@ from pathlib import Path
from harness.logger import logger, initialize_logging, finalize_logging from harness.logger import logger, initialize_logging, finalize_logging
from harness.cli import add_cli_options, set_default_log_formatter from harness.cli import add_cli_options, set_default_log_formatter
from harness.workspace import Workspace, FileCreator from harness.workspace import Workspace, FileCreator
from harness.gkfs import Daemon, Client, Proxy, ShellClient, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator from harness.gkfs import Daemon, Client, ClientLibc, Proxy, ShellClient, ShellClientLibc, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
from harness.reporter import report_test_status, report_test_headline, report_assertion_pass from harness.reporter import report_test_status, report_test_headline, report_assertion_pass
def pytest_configure(config): def pytest_configure(config):
...@@ -159,6 +159,16 @@ def gkfs_client_proxy(test_workspace): ...@@ -159,6 +159,16 @@ def gkfs_client_proxy(test_workspace):
return Client(test_workspace, True) return Client(test_workspace, True)
@pytest.fixture
def gkfs_clientLibc(test_workspace):
"""
Sets up a gekkofs client environment so that
operations (system calls, library calls, ...) can
be requested from a co-running daemon.
"""
return ClientLibc(test_workspace)
@pytest.fixture @pytest.fixture
def gkfs_shell(test_workspace): def gkfs_shell(test_workspace):
""" """
...@@ -177,6 +187,15 @@ def gkfs_shell_proxy(test_workspace): ...@@ -177,6 +187,15 @@ def gkfs_shell_proxy(test_workspace):
return ShellClient(test_workspace,True) return ShellClient(test_workspace,True)
@pytest.fixture
def gkfs_shellLibc(test_workspace):
"""
Sets up a gekkofs environment so that shell commands
(stat, ls, mkdir, etc.) can be issued to a co-running daemon.
"""
return ShellClientLibc(test_workspace)
@pytest.fixture @pytest.fixture
def file_factory(test_workspace): def file_factory(test_workspace):
""" """
......
...@@ -34,7 +34,7 @@ from pathlib import Path ...@@ -34,7 +34,7 @@ from pathlib import Path
from harness.logger import logger, initialize_logging, finalize_logging from harness.logger import logger, initialize_logging, finalize_logging
from harness.cli import add_cli_options, set_default_log_formatter from harness.cli import add_cli_options, set_default_log_formatter
from harness.workspace import Workspace, FileCreator from harness.workspace import Workspace, FileCreator
from harness.gkfs import Daemon, Client, Proxy, ShellClient, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator from harness.gkfs import Daemon, Client, ClientLibc, Proxy, ShellClient, ShellClientLibc, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
from harness.reporter import report_test_status, report_test_headline, report_assertion_pass from harness.reporter import report_test_status, report_test_headline, report_assertion_pass
def pytest_configure(config): def pytest_configure(config):
...@@ -159,6 +159,16 @@ def gkfs_client_proxy(test_workspace): ...@@ -159,6 +159,16 @@ def gkfs_client_proxy(test_workspace):
return Client(test_workspace, True) return Client(test_workspace, True)
@pytest.fixture
def gkfs_clientLibc(test_workspace):
"""
Sets up a gekkofs client environment so that
operations (system calls, library calls, ...) can
be requested from a co-running daemon.
"""
return ClientLibc(test_workspace)
@pytest.fixture @pytest.fixture
def gkfs_shell(test_workspace): def gkfs_shell(test_workspace):
""" """
...@@ -177,6 +187,15 @@ def gkfs_shell_proxy(test_workspace): ...@@ -177,6 +187,15 @@ def gkfs_shell_proxy(test_workspace):
return ShellClient(test_workspace,True) return ShellClient(test_workspace,True)
@pytest.fixture
def gkfs_shellLibc(test_workspace):
"""
Sets up a gekkofs environment so that shell commands
(stat, ls, mkdir, etc.) can be issued to a co-running daemon.
"""
return ShellClientLibc(test_workspace)
@pytest.fixture @pytest.fixture
def file_factory(test_workspace): def file_factory(test_workspace):
""" """
......
...@@ -40,6 +40,7 @@ from harness.cmd import CommandParser ...@@ -40,6 +40,7 @@ from harness.cmd import CommandParser
gkfs_daemon_cmd = 'gkfs_daemon' gkfs_daemon_cmd = 'gkfs_daemon'
gkfs_client_cmd = 'gkfs.io' gkfs_client_cmd = 'gkfs.io'
gkfs_client_lib_file = 'libgkfs_intercept.so' gkfs_client_lib_file = 'libgkfs_intercept.so'
gkfs_client_lib_libc_file = 'libgkfs_libc_intercept.so'
gkfs_hosts_file = 'gkfs_hosts.txt' gkfs_hosts_file = 'gkfs_hosts.txt'
gkfs_daemon_log_file = 'gkfs_daemon.log' gkfs_daemon_log_file = 'gkfs_daemon.log'
gkfs_daemon_log_level = '100' gkfs_daemon_log_level = '100'
...@@ -255,10 +256,10 @@ class Daemon: ...@@ -255,10 +256,10 @@ class Daemon:
def run(self): def run(self):
args = ['--mountdir', self.mountdir, args = ['--mountdir', self.mountdir.as_posix(),
'--rootdir', self.rootdir, '--rootdir', self.rootdir.as_posix(),
'-l', self._address, '-l', self._address,
'--metadir', self._metadir, '--metadir', self._metadir.as_posix(),
'--dbbackend', self._database, '--dbbackend', self._database,
'--output-stats', self.logdir / 'stats.log', '--output-stats', self.logdir / 'stats.log',
'--enable-collection', '--enable-collection',
...@@ -602,6 +603,87 @@ class Client: ...@@ -602,6 +603,87 @@ class Client:
def cwd(self): def cwd(self):
return self._workspace.twd return self._workspace.twd
class ClientLibc:
"""
A class to represent a GekkoFS client process with a patched LD_PRELOAD.
This class allows tests to interact with the file system using I/O-related
function calls, be them system calls (e.g. read()) or glibc I/O functions
(e.g. opendir()).
"""
def __init__(self, workspace):
self._parser = IOParser()
self._workspace = workspace
self._cmd = sh.Command(gkfs_client_cmd, self._workspace.bindirs)
self._env = os.environ.copy()
libdirs = ':'.join(
filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
[str(p) for p in self._workspace.libdirs]))
# ensure the client interception library is available:
# to avoid running code with potentially installed libraries,
# it must be found in one (and only one) of the workspace's bindirs
preloads = []
for d in self._workspace.bindirs:
search_path = Path(d) / gkfs_client_lib_libc_file
if search_path.exists():
preloads.append(search_path)
if len(preloads) == 0:
logger.error(f'No client libraries found in the test\'s binary directories:')
pytest.exit("Aborted due to initialization error. Check test logs.")
if len(preloads) != 1:
logger.error(f'Multiple client libraries found in the test\'s binary directories:')
for p in preloads:
logger.error(f' {p}')
logger.error(f'Make sure that only one copy of the client library is available.')
pytest.exit("Aborted due to initialization error. Check test logs.")
self._preload_library = preloads[0]
self._patched_env = {
'LD_LIBRARY_PATH': libdirs,
'LD_PRELOAD': str(self._preload_library),
'LIBGKFS_HOSTS_FILE': str(self.cwd / gkfs_hosts_file),
'LIBGKFS_LOG': gkfs_client_log_level,
'LIBGKFS_LOG_OUTPUT': str(self._workspace.logdir / gkfs_client_log_file),
'LIBGKFS_LOG_SYSCALL_FILTER': gkfs_client_log_syscall_filter
}
self._env.update(self._patched_env)
@property
def preload_library(self):
"""
Return the preload library detected for this client
"""
return self._preload_library
def run(self, cmd, *args):
logger.debug(f"running client")
logger.debug(f"cmdline: {self._cmd} " + " ".join(map(str, list(args))))
logger.debug(f"patched env: {pformat(self._patched_env)}")
out = self._cmd(
[ cmd ] + list(args),
_env = self._env,
# _out=sys.stdout,
# _err=sys.stderr,
)
logger.debug(f"command output: {out.stdout}")
return self._parser.parse(cmd, out.stdout)
def __getattr__(self, name):
return _proxy_exec(self, name)
@property
def cwd(self):
return self._workspace.twd
class ShellCommand: class ShellCommand:
""" """
A wrapper class for sh.RunningCommand that allows seamlessly using all A wrapper class for sh.RunningCommand that allows seamlessly using all
...@@ -814,6 +896,233 @@ class ShellClient: ...@@ -814,6 +896,233 @@ class ShellClient:
found_cmd = shutil.which(cmd,
path=':'.join(str(p) for p in self._search_paths)
)
if not found_cmd:
raise sh.CommandNotFound(cmd)
self._cmd = sh.Command(found_cmd)
logger.debug(f"running program")
logger.debug(f"cmd: {cmd} {' '.join(str(a) for a in args)}")
logger.debug(f"search_paths: {':'.join(str(p) for p in self._search_paths)}")
logger.debug(f"timeout: {timeout} seconds")
logger.debug(f"timeout_signal: {signal.Signals(timeout_signal).name}")
logger.debug(f"patched env:\n{pformat(self._patched_env)}")
# 'sh' raises an exception if the return code is not zero;
# since we'd rather check for return codes explictly, we
# whitelist all exit codes from 1 to 255 as 'ok' using the
# _ok_code argument
proc = self._cmd(
args,
_env = self._env,
# _out=sys.stdout,
# _err=sys.stderr,
_timeout=timeout,
_timeout_signal=timeout_signal,
# _ok_code=list(range(0, 256))
)
logger.debug(f"program stdout: {proc.stdout}")
logger.debug(f"program stderr: {proc.stderr}")
return ShellCommand(cmd, proc)
def __getattr__(self, name):
return _proxy_exec(self, name)
@property
def cwd(self):
return self._workspace.twd
class ShellClientLibc:
"""
A class to represent a GekkoFS shell client process.
This class allows tests to execute shell commands or scripts via bash -c
on a GekkoFS instance.
"""
def __init__(self, workspace):
self._workspace = workspace
self._search_paths = _find_search_paths(self._workspace.bindirs)
self._env = os.environ.copy()
libdirs = ':'.join(
filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
[str(p) for p in self._workspace.libdirs]))
# ensure the client interception library is available:
# to avoid running code with potentially installed libraries,
# it must be found in one (and only one) of the workspace's bindirs
preloads = []
for d in self._workspace.bindirs:
search_path = Path(d) / gkfs_client_lib_libc_file
if search_path.exists():
preloads.append(search_path)
if len(preloads) != 1:
logger.error(f'Multiple client libraries found in the test\'s binary directories:')
for p in preloads:
logger.error(f' {p}')
logger.error(f'Make sure that only one copy of the client library is available.')
pytest.exit("Aborted due to initialization error")
self._preload_library = preloads[0]
self._patched_env = {
'LD_LIBRARY_PATH' : libdirs,
'LD_PRELOAD' : str(self._preload_library),
'LIBGKFS_HOSTS_FILE' : str(self.cwd / gkfs_hosts_file),
'LIBGKFS_LOG' : gkfs_client_log_level,
'LIBGKFS_LOG_OUTPUT' : str(self._workspace.logdir / gkfs_client_log_file),
'LIBGKFS_LOG_SYSCALL_FILTER': gkfs_client_log_syscall_filter
}
self._env.update(self._patched_env)
@property
def patched_environ(self):
"""
Return the patched environment required to run a test as a string that
can be prepended to a shell command.
"""
return ' '.join(f'{k}="{v}"' for k,v in self._patched_env.items())
def script(self, code, intercept_shell=True, timeout=60, timeout_signal=signal.SIGKILL):
"""
Execute a shell script passed as an argument in bash.
For instance, the following snippet:
mountdir = pathlib.Path('/tmp')
file01 = 'file01'
ShellClient().script(
f'''
expected_pathname={mountdir / file01}
if [[ -e ${{expected_pathname}} ]];
then
exit 0
fi
exit 1
''')
transforms into:
bash -c '
expected_pathname=/tmp/file01
if [[ -e ${expected_pathname} ]];
then
exit 0
fi
exit 1
'
Note that since we are using Python's f-strings, for variable
expansions to work correctly, they need to be defined with double
braces, e.g. ${{expected_pathname}}.
Parameters
----------
code: `str`
The script code to be passed to 'bash -c'.
intercept_shell: `bool`
Controls whether the shell executing the script should be
executed with LD_PRELOAD=libgkfs_intercept.so (default: True).
timeout: `int`
How much time, in seconds, we should give the process to complete.
If the process does not finish within the timeout, it will be sent
the signal defined by `timeout_signal`.
Default value: 60
timeout_signal: `int`
The signal to be sent to the process if `timeout` is not None.
Default value: signal.SIGKILL
Returns
-------
A sh.RunningCommand instance that allows interacting with
the finished process.
"""
logger.debug(f"running bash")
logger.debug(f"cmd: bash -c '{code}'")
logger.debug(f"timeout: {timeout} seconds")
logger.debug(f"timeout_signal: {signal.Signals(timeout_signal).name}")
if intercept_shell:
logger.debug(f"patched env: {self._patched_env}")
self._cmd = sh.Command("bash")
# 'sh' raises an exception if the return code is not zero;
# since we'd rather check for return codes explictly, we
# whitelist all exit codes from 1 to 255 as 'ok' using the
# _ok_code argument
return self._cmd('-c',
code,
_env = (self._env if intercept_shell else os.environ),
# _out=sys.stdout,
# _err=sys.stderr,
_timeout=timeout,
_timeout_signal=timeout_signal,
# _ok_code=list(range(0, 256))
)
def run(self, cmd, *args, timeout=60, timeout_signal=signal.SIGKILL):
"""
Execute a shell command with arguments.
For example, the following snippet:
mountdir = pathlib.Path('/tmp')
file01 = 'file01'
ShellClient().stat('--terse', mountdir / file01)
transforms into:
bash -c 'stat --terse /tmp/file01'
Parameters:
-----------
cmd: `str`
The command to execute.
args: `list`
The list of arguments for the command.
timeout: `number`
How much time, in seconds, we should give the process to complete.
If the process does not finish within the timeout, it will be sent
the signal defined by `timeout_signal`.
Default value: 60
timeout_signal: `int`
The signal to be sent to the process if `timeout` is not None.
Default value: signal.SIGKILL
Returns
-------
A ShellCommand instance that allows interacting with the finished
process. Note that ShellCommand wraps sh.RunningCommand and adds s
extra properties to it.
"""
found_cmd = shutil.which(cmd, found_cmd = shutil.which(cmd,
path=':'.join(str(p) for p in self._search_paths) path=':'.join(str(p) for p in self._search_paths)
) )
......