Last active
October 15, 2024 03:57
-
-
Save jevinskie/43c32ff4772007281e34c83fd1703afd to your computer and use it in GitHub Desktop.
C++23 checked libc function wrappers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// clang-format: off | |
#include "common_internal.hpp" | |
// clang-format: on | |
#include "gsnr/utils.hpp" | |
#include <sys/mman.h> | |
#include <unistd.h> | |
namespace gsnr { | |
int checked_open(const char *GSNR_NONNULL filename, const int flags, const mode_t mode, | |
const std::source_location &loc) { | |
return posix_call_impl(loc, ::open, is_negative<int>, | |
fmt::format("::open(path = \"{:s}\", flags = {:#x}, mode = {:#x})", filename, flags, mode), | |
filename, flags, mode); | |
} | |
int checked_close(const int fildes, const std::source_location &loc) { | |
return posix_call_impl(loc, ::close, is_non_zero<int>, fmt::format("::close(fildes = {:d})", fildes), fildes); | |
} | |
struct stat checked_fstat(const int fildes, const std::source_location &loc) { | |
struct stat buf; | |
posix_call_impl(loc, ::fstat, is_non_zero<int>, | |
fmt::format("::fstat(fildes = {:d}, buf = {})", fildes, fmt::ptr(&buf)), fildes, &buf); | |
return buf; | |
} | |
std::FILE *GSNR_RETURNS_NONNULL checked_fopen(const char *GSNR_NONNULL GSNR_RESTRICT filename, | |
const char *GSNR_NONNULL GSNR_RESTRICT mode, | |
const std::source_location &loc) { | |
return posix_call_impl(loc, std::fopen, is_nullptr<std::FILE *>, | |
fmt::format("std::fopen(path = \"{:s}\", mode = \"{:s}\")", filename, mode), filename, | |
mode); | |
} | |
int checked_fseek(std::FILE *GSNR_NONNULL stream, const long offset, const int origin, | |
const std::source_location &loc) { | |
return posix_call_impl( | |
loc, std::fseek, is_non_zero<int>, | |
fmt::format("std::fseek(stream = {}, offset = {:d}, origin = {:d})", fmt::ptr(stream), offset, origin), stream, | |
offset, origin); | |
} | |
void checked_rewind(std::FILE *GSNR_NONNULL stream, const std::source_location &loc) { | |
posix_call_impl(loc, std::rewind, returns_void, fmt::format("std::rewind(stream = {})", fmt::ptr(stream)), stream); | |
} | |
int checked_fclose(std::FILE *GSNR_NONNULL stream, const std::source_location &loc) { | |
return posix_call_impl(loc, std::fclose, is_non_zero<int>, | |
fmt::format("std::fclose(stream = {})", fmt::ptr(stream)), stream); | |
} | |
int checked_ftell(std::FILE *GSNR_NONNULL stream, const std::source_location &loc) { | |
return posix_call_impl(loc, std::ftell, is_negative<long>, fmt::format("std::ftell(stream = {})", fmt::ptr(stream)), | |
stream); | |
} | |
void *GSNR_RETURNS_NONNULL checked_mmap(const void *GSNR_NULLABLE addr, const size_t len, const int prot, | |
const int flags, const int fildes, const off_t off, | |
const std::source_location &loc) { | |
return posix_call_impl( | |
loc, ::mmap, is_nullptr<void *>, | |
fmt::format("::mmap(addr = {}, len = {:d}, prot = {:#x}, flags = {:#x}, filedes = {:d}, off = {:d})", | |
fmt::ptr(addr), len, prot, flags, fildes, off), | |
const_cast<void *>(addr), len, prot, flags, fildes, off); | |
} | |
int checked_munmap(const void *GSNR_NONNULL addr, const size_t len, const std::source_location &loc) { | |
return posix_call_impl(loc, ::munmap, is_non_zero<int>, | |
fmt::format("::munmap(addr = {}, len = {:d})", fmt::ptr(addr), len), | |
const_cast<void *>(addr), len); | |
} | |
} // namespace gsnr |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "common.hpp" | |
#include <cerrno> // For errno | |
#include <cstdio> | |
#include <cstring> // For strerror_r | |
#include <expected> // For std::expected | |
#include <fcntl.h> | |
#include <limits> | |
#include <source_location> // For std::source_location | |
#include <string> // For std::string | |
#include <sys/mman.h> | |
#include <sys/stat.h> | |
#include <system_error> // For std::system_error | |
#include <type_traits> // For std::is_pointer_v | |
#include <debug_assert.hpp> | |
#include <fmt/format.h> // For fmt::format() | |
namespace gsnr { | |
struct utils_module | |
: debug_assert::default_handler, // use the default handler | |
debug_assert::set_level<std::numeric_limits<unsigned>{}.max()> // level -1, i.e. all assertions, 0 would mean | |
// none, 1 would be level 1, 2 level 2 or lower,... | |
{}; | |
template <typename Func, typename Predicate, typename... Args> | |
auto posix_call_impl(const std::source_location &loc, Func &&func, Predicate failure_condition, | |
const std::string &msg = "", Args &&...args) -> decltype(func(args...)) { | |
using real_res_t = decltype(func(args...)); | |
const auto returns_void = std::is_void_v<real_res_t>; | |
const auto returns_ptr = std::is_pointer_v<real_res_t>; | |
using res_t = std::conditional_t<returns_void, std::string_view, real_res_t>; | |
res_t res; | |
if constexpr (std::is_void_v<decltype(func(args...))>) { | |
res = "void"sv; | |
errno = 0; | |
func(std::forward<Args>(args)...); | |
} else { | |
errno = 0; | |
res = func(std::forward<Args>(args)...); | |
} | |
const auto res_errno = errno; | |
bool failed = true; | |
if constexpr (returns_void) { | |
failed = res_errno != 0; | |
} else { | |
failed = failure_condition(res) || res_errno != 0; | |
} | |
// Check for the failure condition using the provided predicate | |
if (failed) { | |
char buf[256]; | |
const auto strerr_errno = strerror_r(res_errno, buf, sizeof(buf)); | |
if (strerr_errno) { | |
if constexpr (returns_ptr) { | |
GSNR_THROW(std::system_error( | |
res_errno, std::system_category(), | |
fmt::format("\nLocation: {:s}:{:d}:{:d} (in function {:s})\nPOSIX call failed: (strerror_r itself " | |
"failed and returned {:d}){:s} (result: {} errno: {:d})", | |
loc.file_name(), loc.line(), loc.column(), loc.function_name(), strerr_errno, | |
msg.empty() ? msg : " " + msg, fmt::ptr(res), res_errno))); | |
} else { | |
GSNR_THROW(std::system_error( | |
res_errno, std::system_category(), | |
fmt::format("\nLocation: {:s}:{:d}:{:d} (in function {:s})\nPOSIX call failed: (strerror_r itself " | |
"failed and returned {:d}){:s} (result: {} errno: {:d})", | |
loc.file_name(), loc.line(), loc.column(), loc.function_name(), strerr_errno, | |
msg.empty() ? msg : " " + msg, res, res_errno))); | |
} | |
} else { | |
if constexpr (returns_ptr) { | |
GSNR_THROW( | |
std::system_error(res_errno, std::system_category(), | |
fmt::format("\nLocation: {:s}:{:d}:{:d} (in function {:s})\nPOSIX call failed: " | |
"{:s}{:s} (result: {} errno: {:d})", | |
loc.file_name(), loc.line(), loc.column(), loc.function_name(), buf, | |
msg.empty() ? msg : " " + msg, fmt::ptr(res), res_errno))); | |
} else { | |
GSNR_THROW(std::system_error(res_errno, std::system_category(), | |
fmt::format("\nLocation: {:s}:{:d}:{:d} (in function {:s})\nPOSIX call " | |
"failed: {:s}{:s} (result: {} errno: {:d})", | |
loc.file_name(), loc.line(), loc.column(), loc.function_name(), | |
buf, msg.empty() ? msg : " " + msg, res, res_errno))); | |
} | |
} | |
} | |
if constexpr (!returns_void) { | |
return res; | |
} | |
} | |
#define posix_call(func, failure_condition, ...) \ | |
::gsnr::posix_call_impl(std::source_location::current(), func, failure_condition, " " #func " ", __VA_ARGS__) | |
#define posix_call_msg(func, failure_condition, msg, ...) \ | |
::gsnr::posix_call_impl(std::source_location::current(), func, failure_condition, msg, __VA_ARGS__) | |
// Predicates for common failure conditions (using raw function pointers) | |
template <typename T> | |
requires(std::is_integral_v<T> && std::is_signed_v<T>) | |
constexpr bool is_negative(const T result) { | |
return result < 0; | |
} | |
template <typename T> | |
requires(std::is_integral_v<T> && std::is_signed_v<T>) | |
constexpr bool is_non_positive(const T result) { | |
return result <= 0; | |
} | |
template <typename T> | |
requires(std::is_integral_v<T>) | |
constexpr bool is_non_zero(const T result) { | |
return result != 0; | |
} | |
template <typename T> | |
requires(std::is_pointer_v<T>) | |
constexpr bool is_nullptr(const T GSNR_NULLABLE result) { | |
return result == nullptr; | |
} | |
constexpr bool returns_void([[maybe_unused]] const std::string_view res) { | |
return true; | |
} | |
int checked_open(const char *GSNR_NONNULL filename, const int flags, const mode_t mode = 0, | |
const std::source_location &loc = std::source_location::current()); | |
int checked_close(const int filedes, const std::source_location &loc = std::source_location::current()); | |
struct stat checked_fstat(const int fildes, const std::source_location &loc = std::source_location::current()); | |
std::FILE *GSNR_RETURNS_NONNULL checked_fopen(const char *GSNR_NONNULL GSNR_RESTRICT filename, | |
const char *GSNR_NONNULL GSNR_RESTRICT mode, | |
const std::source_location &loc = std::source_location::current()); | |
int checked_fseek(std::FILE *GSNR_NONNULL stream, const long offset, const int origin, | |
const std::source_location &loc = std::source_location::current()); | |
void checked_rewind(std::FILE *GSNR_NONNULL stream, const std::source_location &loc = std::source_location::current()); | |
int checked_fclose(std::FILE *GSNR_NONNULL stream, const std::source_location &loc = std::source_location::current()); | |
int checked_ftell(std::FILE *GSNR_NONNULL stream, const std::source_location &loc = std::source_location::current()); | |
void *GSNR_RETURNS_NONNULL checked_mmap(const void *GSNR_NULLABLE addr, const size_t len, const int prot, | |
const int flags, const int fildes, const off_t off, | |
const std::source_location &loc = std::source_location::current()); | |
int checked_munmap(const void *GSNR_NONNULL addr, const size_t len, | |
const std::source_location &loc = std::source_location::current()); | |
} // namespace gsnr |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment