diff --git a/Makefile b/Makefile index ec85e5a..d2f627e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PROJECT=cow AUTHOR=Avril (Flanchan) VERSION_MAJOR=0 -VERSION_MINOR=1.5 +VERSION_MINOR=2.0r0 VERSION=$(VERSION_MAJOR).$(VERSION_MINOR) ifeq ($(PREFIX),) @@ -156,4 +156,4 @@ uninstall: $(PROJECT)-cpp-test: lib$(PROJECT).so g++ -O3 -flto --std=gnu++20 -Iinclude/ -g -Wall -Wextra src/test/*.cpp -o $@ -l:$< -Wl,-flto -Wl,-O3 - valgrind ./$@ + -valgrind ./$@ diff --git a/include/cow.h b/include/cow.h index 4893973..7a3a6a4 100644 --- a/include/cow.h +++ b/include/cow.h @@ -18,7 +18,13 @@ enum cow_err_kind { COW_ERR_SIZE, /// `mmap()` failed. COW_ERR_MAP, + + _COW_ERR_SIZE, }; +// Message string corresponding to this error. +const char* const * cow_err_msg(enum cow_err_kind kind); +// The last error that `libcow` produced on this thread. +enum cow_err_kind cow_err(); // Copy-on-write mapped memory. typedef struct cow_mapped_slice cow_t; diff --git a/src/cow.c b/src/cow.c index aec3563..6b9367f 100644 --- a/src/cow.c +++ b/src/cow.c @@ -11,25 +11,7 @@ #include -#define LIKELY(ex) __builtin_expect(!!(ex), 1) -#define UNLIKELY(ex) __builtin_expect(!!(ex), 0) - -#define box(t) aligned_alloc(_Alignof(t), sizeof(t)) - -#if defined(DEBUG) || defined(COW_TRACE) -#define TRACE(msg, ...) (fprintf(stderr, " [TRACE] %s->%s():%d: " msg "\n", __FILE__, __func__, __LINE__, __VA_ARGS__), (void)0) -#else -#define TRACE(msg, ...) ((void)0) -#endif - -#if !defined(COW_NO_ASSERT) -#define ASSERT(expr, msg) do { if(!(expr)) die("assertion failed: `" #expr "`: " msg); } while(0) -#else -#define ASSERT(op, msg) ((void)0) -#endif - -#define LASSERT(expr, msg) ASSERT(LIKELY(expr), "(unexpected) " msg) -#define UASSERT(expr, msg) ASSERT(UNLIKELY(expr), "(expected) " msg) +#include "macros.h" // struct cow { ... } #include "cow_t.h" @@ -48,6 +30,10 @@ static __attribute__((noreturn)) __attribute__((noinline)) __attribute__((cold)) static inline cow_t* box_value(cow_t v) { + if(UNLIKELY(v.poisoned)) { + TRACE("WARNING: v is poisoned! not boxing { origin = %p, fd = 0x%x, size = %lu } -> 0 (%lu bytes)", v.origin, v.fd, v.size, sizeof(cow_t)); + return NULL; + } cow_t* boxed = box(cow_t); LASSERT(boxed != NULL, "aligned_alloc() returned `NULL` for `cow_t`"); @@ -69,8 +55,9 @@ static inline int shm_fd(size_t size) #else int fd = memfd_create("cow_create:shm_fd", 0); #endif - if(fd<=0) die("cow_create:shm_fd:memfd_create"); - if(ftruncate(fd, size) != 0) die("cow_create:shm_fd:ftruncate"); + SOFT_ASSERT(fd>0, COW_ERR_FDCREATE, -1); + SOFT_ASSERT(ftruncate(fd, size) == 0, COW_ERR_SIZE, (close(fd), -1)); + return fd; } @@ -93,11 +80,16 @@ inline internal cow_t _cow_create_unboxed(size_t size) { cow_t ret; - //ret.error = COW_POISON_NONE; ret.size = size; - ret.fd = shm_fd(size); + if( (ret.poisoned = + ((ret.fd = shm_fd(size)) == -1)) + ) { + ret.origin = NULL; + return ret; + } ret.origin = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, ret.fd, 0); - if(ret.origin == MAP_FAILED) die("cow_create:mmap"); + + SOFT_ASSERT(ret.origin != MAP_FAILED, COW_ERR_MAP, (ret.poisoned = true, close(ret.fd), ret)); TRACE("mapped new origin cow page of %lu size at %p (memfd %d)", size, ret.origin, ret.fd); return ret; @@ -109,6 +101,10 @@ cow_t* cow_create(size_t size) inline internal void _cow_free_unboxed(const cow_t* cow) { + if(UNLIKELY(cow->poisoned)) { + TRACE("WARNING: attempted to free poisoned object at %p", (const void*)cow); + return; + } TRACE("unmapping %s cow of %lu size from %p (fd %d, real fd %d)", cow_is_fake(cow) ? "fake" : "and closing fd of origin", cow->size, cow->origin, cow->fd, cow_real_fd(cow)); munmap(cow->origin, cow->size); if(!cow_is_fake(cow)) @@ -117,17 +113,25 @@ inline internal void _cow_free_unboxed(const cow_t* cow) void cow_free(cow_t* restrict cow) { + if(UNLIKELY(!cow)) return; + _cow_free_unboxed(cow); free(cow); } cow_t* cow_clone(const cow_t* cow) { + if(UNLIKELY(cow->poisoned)) { + TRACE("WARNING: attempted to clone poisoned object at %p", (const void*)cow); + return NULL; + } cow_t clone; //clone.error = COW_POISON_NONE; + clone.poisoned=false; clone.origin = mmap(cow->origin, cow->size, PROT_READ|PROT_WRITE, MAP_PRIVATE, cow_real_fd(cow), 0); - if(clone.origin == MAP_FAILED) die("cow_clone:mmap"); + SOFT_ASSERT(clone.origin != MAP_FAILED, COW_ERR_MAP, NULL); + clone.fd = (~INT_MAX) | cow->fd; clone.size = cow->size; diff --git a/src/cow.cpp b/src/cow.cpp index 947b21e..2f9fa56 100644 --- a/src/cow.cpp +++ b/src/cow.cpp @@ -2,11 +2,14 @@ #include +#include "macros.h" #include "cow_t.h" + struct Cow::_inner { cow_t cow; + // NOTE: We can assume cow isn't poisoned here, since the constructors throw if it is. inline const cow_t* ptr() const { return &cow; } inline cow_t* ptr() { return &cow; } @@ -19,11 +22,18 @@ struct Cow::_inner { _inner() = delete; }; Cow::_inner::~_inner() { + if(UNLIKELY(cow.poisoned)) return; + _cow_free_unboxed(ptr()); + cow.poisoned=true; +} +Cow::_inner::_inner(size_t sz) : cow(_cow_create_unboxed(sz)){ + //TODO: Real exception type? + if(UNLIKELY(cow.poisoned)) throw "POISONED"; } -Cow::_inner::_inner(size_t sz) : cow(_cow_create_unboxed(sz)){} Cow::_inner::_inner(cow_t* ptr) : cow(*ptr) { + if(UNLIKELY(cow.poisoned)) throw "POISONED"; free(ptr); } diff --git a/src/cow_t.h b/src/cow_t.h index 0865207..003bbfe 100644 --- a/src/cow_t.h +++ b/src/cow_t.h @@ -6,6 +6,8 @@ #ifdef __cplusplus #define restrict __restrict__ extern "C" { +#else +#include #endif #include @@ -20,6 +22,10 @@ struct cow_mapped_slice { size_t size; // Should be at this offset. int fd; // Will be ORd with ~INT_MAX if it's a clone. Will be >0 if it's the original. + + // For unboxed cow_ts. If there was an error constructing, it will be set to `true`. + // If this is true. All resources held by this object will have been freed already. + bool poisoned; }; // cow_t, *cow #ifdef __cplusplus diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..6e6944d --- /dev/null +++ b/src/error.c @@ -0,0 +1,3 @@ +#include "error.h" + +_Thread_local internal enum cow_err_kind _cow_last_error = COW_ERR_NONE; diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..ac3c41c --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,46 @@ +#include + +#include + +#include "error.h" + +namespace _cow_error { + constexpr const size_t SIZE = (size_t)_COW_ERR_SIZE; + + consteval inline void setmsg(std::array& ar, enum cow_err_kind kind, const char* msg) + { + ar[kind] = msg; + } + + consteval inline std::array gen_msg_table() + { + std::array ret; + + setmsg(ret, COW_ERR_UNKNOWN, "unknown error"); + setmsg(ret, COW_ERR_NONE, "success"); + setmsg(ret, COW_ERR_FDCREATE, "failed to create shmfd (memfd_create())"); + setmsg(ret, COW_ERR_SIZE, "failed to set shmfd size (ftruncate())"); + setmsg(ret, COW_ERR_MAP, "failed to map shmfd (mmap())"); + + return ret; + } + + const std::array _cow_message_table = gen_msg_table(); +} +using namespace _cow_error; + +extern "C" { + + enum cow_err_kind cow_err() { + return _cow_last_error; + } + internal void _cow_set_err(enum cow_err_kind kind) { + _cow_last_error = kind; + } + const char* const * cow_err_msg(enum cow_err_kind kind) + { + auto idx = ((size_t)kind); + if ( idx >= SIZE ) return nullptr; + else return &_cow_message_table[idx]; + } +} diff --git a/src/error.h b/src/error.h index eb22bf2..cd0c668 100644 --- a/src/error.h +++ b/src/error.h @@ -9,35 +9,21 @@ #ifdef __cplusplus extern "C" { +#define _Thread_local thread_local #define restrict __restrict__ #endif -/* -Failed: -typedef struct cow_error cow_error; -union poison { - struct cow_error { - cow_error* prev; - - const char* message; - - enum cow_err kind : 31; - /// Should we free this instance? - unsigned char owned : 1; - } e_static; - cow_error* e_global; -}; - -#define COW_ERROR_NONE ((struct cow_error){ .prev = NULL, .message = NULL, .kind = COW_ERR_SUCCESS, .owned = 0 }) -#define COW_POISON_NONE ((union poison){ .e_static = COW_ERROR_NONE}) - -void _cow_poison(cow_t* restrict cow, enum cow_err kind, const char *msg) internal; -void _cow_poison_ref(cow_t* restrict cow, cow_error* globl) internal; -*/ +void _cow_set_err(enum cow_err_kind kind) internal; +extern _Thread_local internal enum cow_err_kind _cow_last_error; + +#define SOFT_ASSERT(ex, kind, ret) do { if(!(ex)) { return (_cow_last_error = (kind), (ret)); } } while(0) +#define SOFT_LASSERT(ex, kind, ret) SOFT_ASSERT(LIKELY(ex), kind, ret) +#define SOFT_UASSERT(ex, kind, ret) SOFT_ASSERT(UNLIKELY(ex), kind, ret) #ifdef __cplusplus } #undef restrict +#undef _Thread_local #endif #endif /* _COW_ERROR_H */ diff --git a/src/macros.h b/src/macros.h index fdfeac3..e7281d5 100644 --- a/src/macros.h +++ b/src/macros.h @@ -3,3 +3,24 @@ #define internal __attribute__((visibility("internal"))) #endif /* internal */ +#define LIKELY(ex) __builtin_expect(!!(ex), 1) +#define UNLIKELY(ex) __builtin_expect(!!(ex), 0) + +#define box(t) aligned_alloc(_Alignof(t), sizeof(t)) + +#if defined(DEBUG) || defined(COW_TRACE) +#define TRACE(msg, ...) (fprintf(stderr, " [TRACE] %s->%s():%d: " msg "\n", __FILE__, __func__, __LINE__, __VA_ARGS__), (void)0) +#else +#define TRACE(msg, ...) ((void)0) +#endif + +#if !defined(COW_NO_ASSERT) +#define ASSERT(expr, msg) do { if(!(expr)) die("assertion failed: `" #expr "`: " msg); } while(0) +#else +#define ASSERT(op, msg) ((void)0) +#endif + +#define LASSERT(expr, msg) ASSERT(LIKELY(expr), "(unexpected) " msg) +#define UASSERT(expr, msg) ASSERT(UNLIKELY(expr), "(expected) " msg) + + diff --git a/src/test/main.cpp b/src/test/main.cpp index 84256dc..7292050 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -216,5 +216,10 @@ int main() printf("First byte of: fake = %x\n", clone[0]); read_fake(clone); //clone still functions because of refcount on origin. + printf("Last error: %d, %s\n", cow_err(), *cow_err_msg(cow_err())); + Cow should_fail(SIZE_MAX); + printf("Last error: %d, %s\n", cow_err(), *cow_err_msg(cow_err())); + Cow::Fake should_fail_clone = should_fail; + printf("Last error: %d, %s\n", cow_err(), *cow_err_msg(cow_err())); return 0; }