Program Listing for File args.hpp

Return to documentation for file (include/client/syscalls/args.hpp)

/*
  Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain
  Copyright 2015-2024, Johannes Gutenberg Universitaet Mainz, Germany

  This software was partially supported by the
  EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).

  This software was partially supported by the
  ADA-FS project under the SPPEXA project funded by the DFG.

  This file is part of GekkoFS' POSIX interface.

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

  GekkoFS' POSIX interface is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

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

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

// This file uses special C formatting for a better overview
// clang-format off

#ifndef GKFS_SYSCALLS_ARGS_HPP
#define GKFS_SYSCALLS_ARGS_HPP

#include <fcntl.h>
#include <csignal>
#include <cassert>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syscall.h>
#include <unistd.h>
#include <optional>

#include <type_traits>
#include <fmt/format.h>
#include <client/syscalls/detail/syscall_info.h>

namespace gkfs::syscall::arg {

enum class type {
    none          = ::arg_type_t::none,
    fd            = ::arg_type_t::fd,
    atfd          = ::arg_type_t::atfd,
    cstr          = ::arg_type_t::cstr,
    open_flags    = ::arg_type_t::open_flags,
    octal_mode    = ::arg_type_t::octal_mode,
    ptr           = ::arg_type_t::ptr,
    dec           = ::arg_type_t::dec,
    dec32         = ::arg_type_t::dec32,
    offset        = ::arg_type_t::offset,
    whence        = ::arg_type_t::whence,
    mmap_prot     = ::arg_type_t::mmap_prot,
    mmap_flags    = ::arg_type_t::mmap_flags,
    clone_flags   = ::arg_type_t::clone_flags,
    signum        = ::arg_type_t::signum,
    sigproc_how   = ::arg_type_t::sigproc_how,
    generic       = ::arg_type_t::arg,
};

/* Some constant definitions for convenience */
static constexpr auto none          = type::none;
static constexpr auto fd            = type::fd;
static constexpr auto atfd          = type::atfd;
static constexpr auto cstr          = type::cstr;
static constexpr auto open_flags    = type::open_flags;
static constexpr auto octal_mode    = type::octal_mode;
static constexpr auto ptr           = type::ptr;
static constexpr auto dec           = type::dec;
static constexpr auto dec32         = type::dec32;
static constexpr auto offset        = type::offset;
static constexpr auto whence        = type::whence;
static constexpr auto mmap_prot     = type::mmap_prot;
static constexpr auto mmap_flags    = type::mmap_flags;
static constexpr auto clone_flags   = type::clone_flags;
static constexpr auto signum        = type::signum;
static constexpr auto sigproc_how   = type::sigproc_how;
static constexpr auto generic       = type::generic;


struct printable_arg {
    const char* const name;
    const long value;
    std::optional<long> size;
};


template <typename FmtBuffer>
using formatter = std::add_pointer_t<void(FmtBuffer&, const printable_arg&)>;


template <typename FmtBuffer> inline void
format_none_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_fd_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_atfd_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_cstr_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_open_flags_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_octal_mode_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_ptr_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_dec_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_dec32_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_whence_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_mmap_prot_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_mmap_flags_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_clone_flags_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_signum_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_sigproc_how_arg_to(FmtBuffer& buffer, const printable_arg& parg);

template <typename FmtBuffer> inline void
format_arg_to(FmtBuffer& buffer, const printable_arg& parg);


template <typename FmtBuffer>
static const constexpr
std::array<formatter<FmtBuffer>, arg_type_max> formatters = {
    /* [none]          = */ format_none_arg_to,
    /* [fd]            = */ format_fd_arg_to,
    /* [atfd]          = */ format_atfd_arg_to,
    /* [cstr]          = */ format_cstr_arg_to,
    /* [open_flags]    = */ format_open_flags_to,
    /* [octal_mode]    = */ format_octal_mode_to,
    /* [ptr]           = */ format_ptr_arg_to,
    /* [dec]           = */ format_dec_arg_to,
    /* [dec32]         = */ format_dec32_arg_to,
    /* [offset]        = */ format_dec_arg_to,
    /* [whence]        = */ format_whence_arg_to,
    /* [mmap_prot]     = */ format_mmap_prot_arg_to,
    /* [mmap_flags]    = */ format_mmap_flags_arg_to,
    /* [clone_flags]   = */ format_clone_flags_arg_to,
    /* [signum]        = */ format_signum_arg_to,
    /* [sigproc_how]   = */ format_sigproc_how_arg_to,
    /* [arg]           = */ format_arg_to,
};

struct desc {
    arg::type type_;
    const char* name_;

    arg::type
    type() const {
        return type_;
    }

    const char*
    name() const {
        return name_;
    }

    template <typename FmtBuffer>
    formatter<FmtBuffer>
    formatter() const {
        const auto idx = static_cast<int>(type_);

        // if the type is unknown fall back to the default formatter
        if(idx < 0 || idx >= static_cast<int>(formatters<FmtBuffer>.size())) {
            return format_arg_to;
        }

        assert(formatters<FmtBuffer>.at(idx) != nullptr);

        return formatters<FmtBuffer>.at(idx);
    }
};


typedef struct {
    long               flag_;
    const char * const name_;
} flag_desc;

#define FLAG_ENTRY(f) flag_desc{ f, #f }

#define LIKELY(x)      __builtin_expect(!!(x), 1)
#define UNLIKELY(x)    __builtin_expect(!!(x), 0)

template <typename FmtBuffer, typename FlagDescriptorArray>
static void
format_flag(FmtBuffer& buffer, long flag, FlagDescriptorArray&& desc) {

    // we assume that if a flag value is zero, its printable
    // name will always be at position 0 in the array
    if(flag == 0 && desc[0].flag_ == 0) {
        fmt::format_to(buffer, "{}", desc[0].name_);
        return;
    }

    for(std::size_t i = 0; i < desc.size(); ++i) {

        if(desc[i].name_ == nullptr) {
            continue;
        }

        if((flag == desc[i].flag_)) {
            fmt::format_to(buffer, "{}", desc[i].name_);
            return;
        }
    }

    fmt::format_to(buffer, "{:#x}", flag);
}

template <typename FmtBuffer, typename FlagDescriptorArray>
static void
format_flag_set(FmtBuffer& buffer, long flags, FlagDescriptorArray&& desc) {

    // we assume that if a flag value is zero, its printable
    // name will always be at position 0 in the array
    if(flags == 0 && desc[0].flag_ == 0) {
        fmt::format_to(buffer, "{}", desc[0].name_);
        return;
    }

    std::size_t i = 0;
    const auto buffer_start = buffer.size();

    while(flags != 0 && i < desc.size()) {

        if(desc[i].name_ == nullptr) {
            ++i;
            continue;
        }

        if((flags & desc[i].flag_) != 0) {
            fmt::format_to(buffer, "{}{}",
                           buffer.size() != buffer_start ? "|" : "",
                           desc[i].name_);
            flags &= ~desc[i].flag_;
        }

        ++i;
    }

    if(flags != 0) {
        if(buffer.size() != buffer_start) {
            fmt::format_to(buffer, "|");
        }

        fmt::format_to(buffer, "{:#x}", flags);
        return;
    }

    if(buffer_start == buffer.size()) {
        fmt::format_to(buffer, "0x0");
    }
}

template <typename FmtBuffer>
inline void
format_whence_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    /* Names for lseek() whence arg */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(SEEK_SET),
            FLAG_ENTRY(SEEK_CUR),
            FLAG_ENTRY(SEEK_END)
        );

    fmt::format_to(buffer, "{}=", parg.name);
    format_flag_set(buffer, parg.value, flag_names);
}


template <typename FmtBuffer>
inline void
format_mmap_prot_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    /* Names for mmap() prot arg */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(PROT_NONE),
            FLAG_ENTRY(PROT_READ),
            FLAG_ENTRY(PROT_WRITE),
            FLAG_ENTRY(PROT_EXEC));

    fmt::format_to(buffer, "{}=", parg.name);
    format_flag_set(buffer, parg.value, flag_names);

    return;
}


template <typename FmtBuffer>
inline void
format_mmap_flags_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    /* Names for mmap() flags arg */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(MAP_SHARED),
            FLAG_ENTRY(MAP_PRIVATE),
#ifdef MAP_SHARED_VALIDATE
            FLAG_ENTRY(MAP_SHARED_VALIDATE),
#endif
            FLAG_ENTRY(MAP_FIXED),
            FLAG_ENTRY(MAP_ANONYMOUS),
            FLAG_ENTRY(MAP_GROWSDOWN),
            FLAG_ENTRY(MAP_DENYWRITE),
            FLAG_ENTRY(MAP_EXECUTABLE),
            FLAG_ENTRY(MAP_LOCKED),
            FLAG_ENTRY(MAP_NORESERVE),
            FLAG_ENTRY(MAP_POPULATE),
            FLAG_ENTRY(MAP_NONBLOCK),
            FLAG_ENTRY(MAP_STACK),
            FLAG_ENTRY(MAP_HUGETLB)
#ifdef MAP_SYNC
            ,
            FLAG_ENTRY(MAP_SYNC)
#endif
            );

    fmt::format_to(buffer, "{}=", parg.name);
    format_flag_set(buffer, parg.value, flag_names);
    return;
}

template <typename FmtBuffer>
inline void
format_clone_flags_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    /* Names for clone() flags arg */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(CLONE_VM),
            FLAG_ENTRY(CLONE_FS),
            FLAG_ENTRY(CLONE_FILES),
            FLAG_ENTRY(CLONE_SIGHAND),
            FLAG_ENTRY(CLONE_PTRACE),
            FLAG_ENTRY(CLONE_VFORK),
            FLAG_ENTRY(CLONE_PARENT),
            FLAG_ENTRY(CLONE_THREAD),
            FLAG_ENTRY(CLONE_NEWNS),
            FLAG_ENTRY(CLONE_SYSVSEM),
            FLAG_ENTRY(CLONE_SETTLS),
            FLAG_ENTRY(CLONE_PARENT_SETTID),
            FLAG_ENTRY(CLONE_CHILD_CLEARTID),
            FLAG_ENTRY(CLONE_DETACHED),
            FLAG_ENTRY(CLONE_UNTRACED),
            FLAG_ENTRY(CLONE_CHILD_SETTID),
#ifdef CLONE_NEWCGROUP
            FLAG_ENTRY(CLONE_NEWCGROUP),
#endif
            FLAG_ENTRY(CLONE_NEWUTS),
            FLAG_ENTRY(CLONE_NEWIPC),
            FLAG_ENTRY(CLONE_NEWUSER),
            FLAG_ENTRY(CLONE_NEWPID),
            FLAG_ENTRY(CLONE_NEWNET),
            FLAG_ENTRY(CLONE_IO));

    fmt::format_to(buffer, "{}=", parg.name);

    // the low byte in clone flags contains the number of the termination
    // signal sent to the parent when the child dies
    format_flag_set(buffer, parg.value & ~0x11l, flag_names);

    if((parg.value & 0x11l) != 0) {
        fmt::format_to(buffer, "|", parg.name);
        format_signum_arg_to(buffer, {"", parg.value & 0x11l});
    }
    return;
}

template <typename FmtBuffer>
inline void
format_signum_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    /* Names for signum args */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(SIGHUP),
            FLAG_ENTRY(SIGINT),
            FLAG_ENTRY(SIGQUIT),
            FLAG_ENTRY(SIGILL),
            FLAG_ENTRY(SIGTRAP),
            FLAG_ENTRY(SIGABRT),
            FLAG_ENTRY(SIGBUS),
            FLAG_ENTRY(SIGFPE),
            FLAG_ENTRY(SIGKILL),
            FLAG_ENTRY(SIGUSR1),
            FLAG_ENTRY(SIGSEGV),
            FLAG_ENTRY(SIGUSR2),
            FLAG_ENTRY(SIGPIPE),
            FLAG_ENTRY(SIGALRM),
            FLAG_ENTRY(SIGTERM),
            FLAG_ENTRY(SIGSTKFLT),
            FLAG_ENTRY(SIGCHLD),
            FLAG_ENTRY(SIGCONT),
            FLAG_ENTRY(SIGSTOP),
            FLAG_ENTRY(SIGTSTP),
            FLAG_ENTRY(SIGTTIN),
            FLAG_ENTRY(SIGTTOU),
            FLAG_ENTRY(SIGURG),
            FLAG_ENTRY(SIGXCPU),
            FLAG_ENTRY(SIGXFSZ),
            FLAG_ENTRY(SIGVTALRM),
            FLAG_ENTRY(SIGPROF),
            FLAG_ENTRY(SIGWINCH),
            FLAG_ENTRY(SIGIO),
            FLAG_ENTRY(SIGPWR),
            FLAG_ENTRY(SIGSYS));

    if(std::strcmp(parg.name, "")) {
        fmt::format_to(buffer, "{}=", parg.name);
    }

    format_flag(buffer, parg.value, flag_names);
    return;
}


template <typename FmtBuffer>
inline void
format_sigproc_how_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    /* Names for sigproc how args */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(SIG_BLOCK),
            FLAG_ENTRY(SIG_UNBLOCK),
            FLAG_ENTRY(SIG_SETMASK));

    fmt::format_to(buffer, "{}=", parg.name);
    format_flag(buffer, parg.value, flag_names);
    return;
}

template <typename FmtBuffer>
inline void
format_none_arg_to(FmtBuffer& buffer, const printable_arg& parg) {
    fmt::format_to(buffer, "void");
}


template <typename FmtBuffer>
inline void
format_fd_arg_to(FmtBuffer& buffer, const printable_arg& parg) {
    fmt::format_to(buffer, "{}={}", parg.name, static_cast<int>(parg.value));
}


template <typename FmtBuffer>
inline void
format_atfd_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    if(static_cast<int>(parg.value) == AT_FDCWD) {
        fmt::format_to(buffer, "{}=AT_FDCWD", parg.name);
        return;
    }

    fmt::format_to(buffer, "{}={}", parg.name, static_cast<int>(parg.value));
}


template <typename FmtBuffer>
inline void
format_cstr_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    if(LIKELY(reinterpret_cast<const char*>(parg.value) != nullptr)) {
        fmt::format_to(buffer, "{}=\"{}\"", parg.name,
                       reinterpret_cast<const char*>(parg.value));
        return;
    }

    fmt::format_to(buffer, "{}=NULL", parg.name);
}

template <typename FmtBuffer>
inline void
format_open_flags_to(FmtBuffer& buffer,
                     const printable_arg& parg) {

    /* Names for O_ACCMODE args */
    const auto flag_names =
            utils::make_array(
            FLAG_ENTRY(O_RDONLY),
            FLAG_ENTRY(O_WRONLY),
            FLAG_ENTRY(O_RDWR));

    const auto extra_flag_names =
            utils::make_array(
#ifdef O_EXEC
            FLAG_ENTRY(O_EXEC),
#endif
#ifdef O_SEARCH
            FLAG_ENTRY(O_SEARCH),
#endif
            FLAG_ENTRY(O_APPEND),
            FLAG_ENTRY(O_CLOEXEC),
            FLAG_ENTRY(O_CREAT),
            FLAG_ENTRY(O_DIRECTORY),
            FLAG_ENTRY(O_DSYNC),
            FLAG_ENTRY(O_EXCL),
            FLAG_ENTRY(O_NOCTTY),
            FLAG_ENTRY(O_NOFOLLOW),
            FLAG_ENTRY(O_NONBLOCK),
            FLAG_ENTRY(O_RSYNC),
            FLAG_ENTRY(O_SYNC),
            FLAG_ENTRY(O_TRUNC)
#ifdef O_TTY_INIT
            , FLAG_ENTRY(O_TTY_INIT)
#endif
            );

    long flags = parg.value;

    fmt::format_to(buffer, "{}=", parg.name);
    format_flag(buffer, flags & O_ACCMODE, flag_names);

    flags &= ~O_ACCMODE;

#ifdef O_TMPFILE
    // processing it with the other flags can result in
    // printing O_DIRECTORY when it should not be listed.
    //
    // See O_TMPFILE' definition in fcntl-linux.h :
    //     #define __O_TMPFILE   (020000000 | __O_DIRECTORY)
    if((flags & O_TMPFILE) == O_TMPFILE) {
        format_flag(buffer, O_TMPFILE, flag_names);
        flags &= ~O_TMPFILE;
    }
#endif // !O_TMPFILE

    if(flags != 0) {
        fmt::format_to(buffer, "|", parg.name);
        format_flag_set(buffer, flags, extra_flag_names);
    }
}

template <typename FmtBuffer>
inline void
format_octal_mode_to(FmtBuffer& buffer, const printable_arg& parg) {
    fmt::format_to(buffer, "{}={:#04o}", parg.name, parg.value);
}

template <typename FmtBuffer>
inline void
format_ptr_arg_to(FmtBuffer& buffer, const printable_arg& parg) {

    if(LIKELY(reinterpret_cast<const void*>(parg.value) != nullptr)) {
        fmt::format_to(buffer, "{}={}", parg.name,
                       reinterpret_cast<const void*>(parg.value));
        return;
    }

    fmt::format_to(buffer, "{}=NULL", parg.name);
}


template <typename FmtBuffer>
inline void
format_dec_arg_to(FmtBuffer& buffer, const printable_arg& parg) {
    fmt::format_to(buffer, "{}={}", parg.name, parg.value);
}


template <typename FmtBuffer>
inline void
format_dec32_arg_to(FmtBuffer& buffer, const printable_arg& parg) {
    fmt::format_to(buffer, "{}={}", parg.name, static_cast<int>(parg.value));
}


template <typename FmtBuffer>
inline void
format_arg_to(FmtBuffer& buffer, const printable_arg& parg) {
    fmt::format_to(buffer, "{}={:#x}", parg.name, parg.value);
}

#undef FLAG_ENTRY
#undef LIKELY
#undef UNLIKELY

} // namespace gkfs::syscall::arg

#endif // GKFS_SYSCALLS_ARGS_HPP

// clang-format on