Compare commits

...

24 Commits

Author SHA1 Message Date
Avril 130f21f57e
Removed rust version from master
1 year ago
Avril 671fcd3968
Merge branch 'rust': Wrong branch, whoops.
1 year ago
Avril 0d082f5b6f
Added `cow_create_fd(fd, size)`: Create a `cow_t*` over an already existing file descriptor
1 year ago
Avril 6f21491298
typed OO bullshit
3 years ago
Avril 2d4ebc3142
error type
3 years ago
Avril 7b623e0fdf
rust bindings: start
3 years ago
Avril 7e4e3f9867
kill me
3 years ago
Avril 646482c555
scrapped
3 years ago
Avril 2c88d329b9
typed cow testing n stuff
3 years ago
Avril 382efd5d43
started TypedCow <cow/typed.hpp>
3 years ago
Avril ffe01128c4
version bump
3 years ago
Avril 30a0176604
added COW_ERR_POISON
3 years ago
Avril 960f18a6e0
c++: CowException for handling errors/poisoned cows
3 years ago
Avril 8f8d738e0e
c++: CowException for handling errors/poisoned cows
3 years ago
Avril a293133f29
basic error skel
3 years ago
Avril a5d5d1b9fd
reset: enum cow_err_kind exists
3 years ago
Avril aeb3f528d4
start recoverable error handle: failed design
3 years ago
Avril 8d5e587333
start TypedCow
3 years ago
Avril fcd0597dc8
fix null deref on moved Cow
3 years ago
Avril 1672250ed6
updated README
3 years ago
Avril 8334792d8c
idek anymore
3 years ago
Avril ee1b9af2b9
fix SONAME version convension
3 years ago
Avril 79517ad97a
added correct compiler/linker flags to .so targets
3 years ago
Avril a834612e23
correct error handling of ftruncate
3 years ago

4
.gitignore vendored

@ -1,6 +1,8 @@
cow-*
*.a
*.so
*.so*
obj/
*~
vgcore.*
target/
!cowslice/*.a

@ -3,7 +3,10 @@
PROJECT=cow
AUTHOR=Avril (Flanchan) <flanchan@cumallover.me>
VERSION=0.1.1
VERSION_MAJOR=0
VERSION_MINOR=3.0
VERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
ifeq ($(PREFIX),)
PREFIX := /usr/local
@ -25,14 +28,14 @@ CXX_OPT_FLAGS?= $(OPT_FLAGS)
CFLAGS += $(COMMON_FLAGS) --std=gnu11
CXXFLAGS += $(COMMON_FLAGS) --std=gnu++20 -felide-constructors
LDFLAGS +=
LDFLAGS +=
STRIP=strip
ifneq ($(TARGET_SPEC_FLAGS),no)
RELEASE_CFLAGS?= -O3 -flto $(OPT_FLAGS)
RELEASE_CXXFLAGS?= -O3 -flto $(CXX_OPT_FLAGS)
RELEASE_LDFLAGS?= -O3 -flto
RELEASE_LDFLAGS?= -Wl,-O3 -Wl,-flto
DEBUG_CFLAGS?= -O0 -g
DEBUG_CXXFLAGS?=-O0 -g
@ -98,52 +101,59 @@ lib$(PROJECT)-release.a: CXXFLAGS += $(RELEASE_CXXFLAGS)
lib$(PROJECT)-release.a: LDFLAGS += $(RELEASE_LDFLAGS)
lib$(PROJECT)-release.a: $(OBJ)
ar rcs $@ $^
ranlib $@
lib$(PROJECT)-debug.a: CFLAGS+= $(DEBUG_CFLAGS)
lib$(PROJECT)-debug.a: CXXFLAGS += $(DEBUG_CXXFLAGS)
lib$(PROJECT)-debug.a: LDFLAGS += $(DEBUG_LDFLAGS)
lib$(PROJECT)-debug.a: $(OBJ)
ar rcs $@ $^
ranlib $@
lib$(PROJECT)-release.so: CFLAGS+= $(RELEASE_CFLAGS) -fPIC
lib$(PROJECT)-release.so: CXXFLAGS += $(RELEASE_CXXFLAGS) -fPIC
lib$(PROJECT)-release.so: LDFLAGS += $(RELEASE_LDFLAGS)
lib$(PROJECT)-release.so: $(OBJ)
$(CXX) -shared $^ -o $@
$(CXX) -shared $^ $(CXXFLAGS) -o $@ $(LDFLAGS)
$(STRIP) $@
lib$(PROJECT)-debug.so: CFLAGS+= $(DEBUG_CFLAGS) -fPIC
lib$(PROJECT)-debug.so: CXXFLAGS += $(DEBUG_CXXFLAGS) -fPIC
lib$(PROJECT)-debug.so: LDFLAGS += $(DEBUG_LDFLAGS)
lib$(PROJECT)-debug.so: $(OBJ)
$(CXX) -shared $^ -o $@
$(CXX) -shared $^ $(CXXFLAGS) -o $@ $(LDFLAGS)
lib$(PROJECT).a: lib$(PROJECT)-release.a
ln -f $< $@
lib$(PROJECT).so: LDFLAGS+= -Wl,-soname,lib$(PROJECT).so.$(VERSION_MAJOR)
lib$(PROJECT).so: lib$(PROJECT)-release.so
ln -f $< $@
ln -f $< $@.$(VERSION)
ln -sf $@.$(VERSION) $@.$(VERSION_MAJOR)
ln -sf $@.$(VERSION_MAJOR) $@
clean-rebuild:
rm -rf obj
clean: clean-rebuild
rm -f lib$(PROJECT){,-{release,debug,pgo}}.{a,so}
rm -f lib$(PROJECT){,-{release,debug,pgo}}.{a,so{,.*}}
install:
install -d $(DESTDIR)$(PREFIX)/lib/
install -m 644 lib$(PROJECT).a $(DESTDIR)$(PREFIX)/lib/
install -m 755 lib$(PROJECT).so $(DESTDIR)$(PREFIX)/lib/
install -s -m 755 lib$(PROJECT).so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/
ln -sf lib$(PROJECT).so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).so.$(VERSION_MAJOR)
ln -sf lib$(PROJECT).so.$(VERSION_MAJOR) $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).so
install -d $(DESTDIR)$(PREFIX)/include/
install -m 644 $(wildcard $(INCLUDE)/*.*) $(DESTDIR)$(PREFIX)/include/
install -d $(DESTDIR)$(PREFIX)/include/$(PROJECT)/
install -m 644 $(wildcard $(INCLUDE)/$(PROJECT)/*.*) $(DESTDIR)$(PREFIX)/include/$(PROJECT)/
uninstall:
-rm $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).{a,so}
-rm $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).{a,so{,.*}}
cd $(INCLUDE) && find . -type f | xargs -I {} rm "$(DESTDIR)$(PREFIX)/include/{}"
-rmdir $(DESTDIR)$(PREFIX)/include/$(PROJECT)
$(PROJECT)-cpp-test: lib$(PROJECT).a
g++ -O3 --std=gnu++20 -Iinclude/ -g -Wall -Wextra src/test/*.cpp -o $@ -l:$<
valgrind ./$@
$(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 ./$@

@ -1,11 +1,12 @@
# libcow
Automatic copy-on-write semantic memory slices for use in C (and C++)
Automatic copy-on-write semantic memory slices library for use in C and C++.
# Usage
See `include/cow.h` for documentation on each function.
See `include/cow.hpp` for the C++ wrapper API class.
## C API
Each function, macro, and type definition in the header will be prefixed with `cow_` or `COW_`. Internal non-prototpyed items use the namespace `_cow_` or `_COW_`.
Each function, macro, and type definition in the header will be prefixed with `cow_` or `COW_`. Internal and/or non-prototpyed items use the namespace `_cow_` or `_COW_`.
### C++ wrapper API
The C++ interface defines the type `Cow`, a reference-counted wrapper over `cow_t` instances that supports cloning through its subtype, `Cow::Fake`, and automatically ensures the originally created `cow_t` is not destroyed until all its clones are, as well as the namespace `_cow_util` which contains memory accessor helpers `Span<T>` and `Slice<T>` (aka `Span<T>::Slice`).
@ -16,11 +17,11 @@ There are also the following:
## Building
Run `make` to build to build the `release` (optimised) target of the library.
It will create four files: `libcow-release.a`, `libcow-release.so`, `libcow.a`, and `libcow.so`.
The latter two are just symlinks to the former two.
It will create four files: `libcow-release.a`, `libcow-release.so`, `libcow.a`, and `libcow.so` (wish `SONAME` versioned symlinks).
The latter two are hardlinked to the former two.
Run `make debug` to build the debug target, which disables optimisations and includes trace messages.
It will create two files: `libcow-debug.a` and `libcow-debug.so`.
It will create two files: `libcow-debug.a` and `libcow-debug.so`. The debug target `.so` does not include a `SONAME`, nor does it produce the versioning symlinks (unless you manually set `LDFLAGS="-Wl,-soname,libcow.so.<version>"` and create the symlinks afterwards.)
Each target compiles both a static and dynamic library. You may need to run `make clean` before switching build targets.
To build both targets, run `make all`.
@ -32,29 +33,29 @@ Run `sudo make uninstall` to remove the libraries and header files.
By default, the install target is `/usr/local/`. Set the `PREFIX` variable when running `make install` / `make uninstall` to specify a different path.
### Full build and installation
## Installing
To build and install with the default configuration.
```shell
$ make && sudo make install
```
Will build with the default optimisation configuration and install the following files/directories:
Will build with the default optimisations enabled and install the following files/directories:
* /usr/local/lib/libcow.a
* /usr/local/lib/libcow.so
* /usr/local/lib/libcow.so (with `SONAME` versioned symlinks)
* /usr/local/include/cow.h
* /usr/local/include/cow.hpp
* /usr/local/include/cow/
### Notes
## Notes
* The `release` target specifies `-march=native` by default. This may be undesirable, if so, set `TARGET_CPU=""` when running `make`.
* Many optimisation flags for the `release` configuration are specific to GCC (with graphite enabled by default), if builds on other compilers (or non-graphite enabled GCC builds) complain, either set the `OPT_FLAGS` env var or remove the problem flags from the Makefile.
* `release` builds are stripped by default. run `make STRIP=: release` to prevent stripping.
* The targets are all built with `-fno-strict-aliasing`, but functions in the header file are still annotated with `restrict` needed. This is just to inform users that the function will assume the pointer is not aliased. (When included in C++, where `restrict` is not a keyword, we temporarily define it to be `__restrict__`, which is the GCC equivalent for C++).
* The `debug` target `.so` does not include a `SONAME`, nor does it produce the output symlinks expected of a `SONAME`. The `release` target does. The version is specified in the Makefile.
## Using
Link to either `libcow.a` or `libcow.so` (or the debug target libraries), and include the header `include/cow.h` to your project to use this library.
The header should work in C++ projects as well.
The header should work in C++ projects as well, but there is a C++-specific wrapper API in `include/cow.hpp` which you can use instead for automatic handling of resources (*see above*).
# Requirements
Relying on the `memfd_create()` syscall, Linux >=3.17 and glibc >=2.27 (or equivalent) are required for build.
@ -99,6 +100,52 @@ Fake: Hello fake!
```
Notice the first read of `fake` contains the data written to `origin`. And that the write of `Hello fake!` caused only `fake` to be updated, not `origin`.
## C++ API example
``` c++
#include <cow.hpp>
#include <cstring>
#include <cstdio>
void write_cow(Cow& to, const char* string)
{
strncpy(to.area_as<char>(), string, to.size()-1);
}
void read_cow(const Cow& from)
{
printf("Cow says: %s\n", from.area_as<char>());
}
int main()
{
Cow area(1024);
write_cow(area, "Initial state");
Cow::Fake clone = area;
read_cow(clone);
write_cow(clone, "Cloned state");
read_cow(clone);
read_cow(area);
return 0;
}
```
Will print:
``` shell
$ ./test
Cow says: Initial state
Cow says: Cloned state
Cow says: Initial state
```
The `Cow` class and its subclass `Cow::Fake` handles freeing resources automatically. Alternatively, there is the `Area` class which can act as both (see `cow/area.hpp`).
## What is happening here?
The cloned slice, `fake`, which is created from `origin` with the `cow_clone()` function will contain all the information within `origin`.
The cloned slice can be written to, however, those writes will only be visible to that specific instance of `cow_t`, even if that `cow_t*` is again `cow_clone()`d.

@ -8,6 +8,26 @@ extern "C" {
#include <stdlib.h>
enum cow_err_kind {
COW_ERR_UNKNOWN =0,
COW_ERR_NONE =1, // Success
/// `memfd_create()` failed.
COW_ERR_FDCREATE,
/// `ftruncate()` (set size) failed.
COW_ERR_SIZE,
/// `mmap()` failed.
COW_ERR_MAP,
/// Trying to use a poisoned cow
COW_ERR_POISONED,
_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;
@ -15,6 +35,13 @@ typedef struct cow_mapped_slice cow_t;
/// Writes to this instance pointer (`cow_ptr()`) are written to the allocated memory.
/// Writes to any cloned instances do not propagate to this instance.
cow_t* cow_create(size_t size);
/// Create a new copy-on-write area of `size` bytes from file `fd`.
/// Writes to this instance pointer (`cow_ptr()`) are written to the allocated memory.
/// Writes to any cloned instances do not propagate to this instance.
///
/// The resulting object does not own `fd`, but does own a duplicate of it.
cow_t* cow_create_fd(int fd, size_t size);
/// Free a cow area. This should be called on all clones before the parent of those clones is freed.
void cow_free(cow_t* restrict cow);
/// Create a clone of this instance. Any writes to the returned pointer will not be propagated to the input one.
@ -31,7 +58,7 @@ static inline
#ifdef _COW_NO_ASSUME_ABI
#define _cow_size_unsafe(v) cow_size(v)
#else
// XXX: This macro is *VERY* ABI sensitive. This shouldn't be used if the ABI has changed since the build of libcow's `cow_t.h` passed its static assertions in *both* the C and C++ implementations.
// XXX: This macro is *VERY* ABI sensitive. This shouldn't be used if the ABI has changed since the build of libcow's `cow_t.h` passed its stati assertions in *both* the C and C++ implementations.
// The C++ API uses this by default for its `Cow::size()` function.
#define _cow_size_unsafe(v) (*(((size_t*)(v))+1))
__attribute__((deprecated("size() is safer and offers better codegen.")))

@ -3,21 +3,42 @@
#include "cow.h"
#include <memory>
#include <exception>
#include "cow/slice.hpp"
struct CowException : public std::exception
{
inline CowException(cow_err_kind k) : kind(k){}
const char* what() const noexcept override;
inline ~CowException(){}
const cow_err_kind kind;
};
struct Cow : public _cow_util::Span<unsigned char> {
struct Fake;
Cow() = delete;
Cow(int fd, size_t size);
explicit Cow(size_t size);
Cow(Cow&& m);
virtual ~Cow();
virtual Fake clone() const;
inline void* area() override { return cow_ptr(get_raw()); }
inline const void* area() const override { return cow_ptr_of(const void, get_raw()); }
protected:
inline void* area() override {
auto raw = get_raw();
return raw ? cow_ptr(raw) : nullptr;
}
inline const void* area() const override {
auto raw = get_raw();
return raw ? cow_ptr_of(const void, raw) : nullptr;
}
public:
/// Get the size of the mapped area.
///
@ -42,6 +63,8 @@ struct Cow : public _cow_util::Span<unsigned char> {
protected:
Cow(const Cow& c);
explicit inline Cow(std::shared_ptr<_inner>&& super) : super(std::move(super)){}
const std::shared_ptr<_inner> super;
virtual cow_t* get_raw() const;
@ -66,3 +89,4 @@ struct Cow::Fake : public Cow {
cow_t* const fake;
};

@ -5,9 +5,10 @@
#include <utility>
#include <cow.hpp>
#include "slice.hpp"
namespace _cow_util {
struct Area {
struct Area : public Span<unsigned char> {
Area() = delete;
explicit Area(size_t sz);
@ -30,7 +31,13 @@ namespace _cow_util {
inline cow_t* raw() const { return _area->raw(); }
inline size_t size() const override { return _area->size(); }
~Area();
protected:
inline void* area() override { return _area->ptr(); }
inline const void* area() const override { return _area->ptr(); }
private:
const std::unique_ptr<Cow> _area;
};

@ -4,17 +4,19 @@ namespace _cow_util {
/// A type that spans a sized region of memory
template<typename T>
struct Span {
protected:
virtual const void* area() const =0;
virtual void* area() = 0;
public:
virtual size_t size() const = 0;
inline T* ptr() { return (T*)area(); }
inline const T* ptr() const { return (const T*)area(); }
inline T* ptr() { return reinterpret_cast<T*>(area()); }
inline const T* ptr() const { return reinterpret_cast<const T*>(area()); }
inline size_t size_bytes() const { return size() * sizeof(T); }
inline unsigned char* as_bytes() { return (unsigned char*)area(); }
inline const unsigned char* as_bytes() const { return (const unsigned char*)area(); }
inline unsigned char* as_bytes() { return area_as<unsigned char>(); }
inline const unsigned char* as_bytes() const { return area_as<unsigned char>(); }
inline T& operator[](size_t index) {
if(index >= size()) throw "index out of range";
@ -38,9 +40,9 @@ namespace _cow_util {
inline operator T*() { return &(*this)[0]; }
template<typename U>
inline U* area_as() requires(sizeof(T) % sizeof(U) == 0) { return (U*)area(); }
inline U* area_as() requires(sizeof(T) % sizeof(U) == 0) { return reinterpret_cast<U*>(area()); }
template<typename U>
inline const U* area_as() const requires(sizeof(T) % sizeof(U) == 0) { return (U*)area(); }
inline const U* area_as() const requires(sizeof(T) % sizeof(U) == 0) { return reinterpret_cast<const U*>(area()); }
template<typename U>
size_t size_as() const requires(sizeof(T) % sizeof(U) == 0) { return size_bytes() / sizeof(U); }
@ -89,23 +91,24 @@ namespace _cow_util {
inline const Slice slice_wrap(ssize_t len) const { return slice_abs((size_t)wrap_len(len)); }
template<typename U>
inline Span<U>::Slice reinterpret() { return typename Span<U>::Slice((U*)area(), size_bytes() / sizeof(U)); }
inline Span<U>::Slice reinterpret() { return typename Span<U>::Slice(area_as<U>(), size_bytes() / sizeof(U)); }
template<typename U>
inline Span<const U>::Slice reinterpret() const { return typename Span<const U>::Slice((const U*)area(), size_bytes() / sizeof(U)); }
inline Span<const U>::Slice reinterpret() const { return typename Span<const U>::Slice(area_as<U>(), size_bytes() / sizeof(U)); }
};
/// A slice of memory with a backing pointer and size.
template<typename T>
struct Span<T>::Slice : public Span<T> {
inline Slice(T* ptr, size_t sz) : _area((void*)ptr), _size(sz){}
inline Slice(T* ptr, size_t sz) : _area(reinterpret_cast<void*>(ptr)), _size(sz){}
inline Slice(const Span<T>& slice) : _area(const_cast<void*>(slice.area())), _size(slice.size()){}
inline Slice(const Slice& copy) = default;
inline Slice(Slice&& copy) : _area(copy._area), _size(copy._size){ *const_cast<size_t*>(&copy._size) = 0; }
Slice() = delete;
inline size_t size() const override { return _size; }
protected:
inline const void* area() const override { return _area; }
inline void* area() override { return _area; }
inline size_t size() const override { return _size; }
private:
void* const _area;

@ -0,0 +1,121 @@
#pragma once
#include <cow.hpp>
#include <utility>
template<typename T>
struct TypedCow : public _cow_util::Span<T> {
struct Fake;
template<typename... Args>
inline TypedCow(size_t sz, Args&&... args) : real(Cow(sz * sizeof(T))) { init_copy( T(std::forward<Args>(args)...) ); }
inline TypedCow(size_t sz, const T& copy) : real(Cow(sz * sizeof(T))) { init_copy(copy); }
inline TypedCow(size_t sz) : TypedCow(sz, T()){}
inline virtual ~TypedCow() { uninit(); }
inline virtual Fake clone() const { return Fake(*this); }
inline virtual cow_t* raw() const { return real.raw(); }
inline size_t size() const { return real.size() / sizeof(T); }
protected:
//inline explicit TypedCow(const TypedCow<T>& unsafeCopy) : real(Cow(unsafeCopy.real)){}
inline virtual void* area() override { return reinterpret_cast<void*>( real.ptr() ); }
inline virtual const void* area() const override { return reinterpret_cast<const void*>( real.ptr() ); }
inline void init_copy(const T& copy_from) {
T* ptr = this->ptr();
for(size_t i=0;i<size();i++)
new ((void*)(ptr+i)) T(copy_from);
}
// UNSAFE: Explicitly calls destructors of each element in this instance.
inline void uninit() {
T* ptr = this->ptr();
if(!ptr) return;
for(size_t i=0;i<size();i++)
(ptr+i)->~T();
}
private:
Cow real;
};
template<typename T>
struct TypedCow<T>::Fake : public TypedCow<T> {
//XXX: How THE FUCK do we initialise base's `real` here?????
Fake() = delete;
inline Fake(const Fake& copy) : fake(cow_clone(copy.fake)){}
inline Fake(Fake&& move) : fake(move.fake) { *const_cast<cow_t**>(&move.fake) = nullptr; }
inline Fake(const TypedCow<T>& clone) : fake(cow_clone(clone.raw())){}
inline cow_t* raw() const override { return fake; }
inline Fake clone() const override { return Fake(*this); }
inline ~Fake(){}
inline size_t size() const override { return fake ? cow_size(fake) : 0; }
protected:
inline void* area() override { return fake ? cow_ptr(fake) : nullptr; }
inline const void* area() const override { return fake ? cow_ptr_of(const void, fake) : nullptr; }
private:
cow_t* const fake;
};
#if 0
struct Fake;
friend class Fake;
TypedCow() = delete;
inline TypedCow(TypedCow<T>&& move) : Cow(std::move(move.super)) { /* Moves the shared_ptr. No need to move the elements. */ }
// protected: copy
inline TypedCow(size_t sz) : Cow(sz * sizeof(T)) { init_copy(T()); }
inline TypedCow(size_t sz, const T& copy_from) : TypedCow(sz) { init_copy(copy_from); }
//template<typename... Args>
//inline TypedCow(size_t sz, Args&&... args) : TypedCow(sz) { init_copy( T(std::forward<Args>(args)...) ); }
virtual inline ~TypedCow() { uninit(); }
inline Cow::Fake clone() const override { return Cow::clone(); }
inline Fake clone() const { return Fake(Cow::clone()); }
inline size_t size() const override { return Cow::size() / sizeof(T); }
protected:
inline void* area() override { return Cow::area(); }
inline const void* area() const override { return Cow::area(); }
//// Should only be used for creating Fakes. Copies the refcounted pointer.
//inline TypedCow(const TypedCow<T>& copy) : Cow(copy.super) {}
// UNSAFE: Placement-new's copys of `copy_from` into `0..size()` of this instance.
inline void init_copy(const T& copy_from) {
T* ptr = _cow_util::Span<T>::ptr();
for(size_t i=0;i<size();i++)
new ((void*)(ptr+i)) T(copy_from);
}
// UNSAFE: Explicitly calls destructors of each element in this instance.
inline void uninit() {
T* ptr = _cow_util::Span<T>::ptr();
for(size_t i=0;i<size();i++)
(ptr+i)->~T();
}
};
template<typename T>
struct TypedCow<T>::Fake : private Cow::Fake, public _cow_util::Span<T> {
Fake() = delete;
explicit inline Fake(Cow::Fake&& untyped) : Cow::Fake(untyped){}
inline Fake(const Fake& copy) : Fake(copy.Cow::Fake::clone()){}
inline Fake(Fake&& move) : Cow::Fake(std::move(move)) {}
inline ~Fake(){}
inline size_t size() const override { return Cow::Fake::size(); }
protected:
inline const void* area() const override { return Cow::Fake::area(); }
inline void* area() override { return Cow::Fake::area(); }
};
#endif

@ -11,28 +11,11 @@
#include <cow.h>
#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, "<libcow> [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"
#include "error.h"
static __attribute__((noreturn)) __attribute__((noinline)) __attribute__((cold)) void die(const char* error)
{
@ -47,7 +30,13 @@ 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`");
TRACE("boxing cow_t { origin = %p, fd = 0x%x, size = %lu } -> %p (%lu bytes)", v.origin, v.fd, v.size, (const void*)boxed, sizeof(cow_t));
*boxed = v;
@ -66,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");
ftruncate(fd, size);
SOFT_ASSERT(fd>0, COW_ERR_FDCREATE, -1);
SOFT_ASSERT(ftruncate(fd, size) == 0, COW_ERR_SIZE, (close(fd), -1));
return fd;
}
@ -86,24 +76,42 @@ size_t cow_size(const cow_t* cow)
return cow->size;
}
inline internal cow_t _cow_create_unboxed(size_t size)
inline internal cow_t _cow_create_unboxed(int _fd, size_t size)
{
cow_t ret;
ret.size = size;
ret.fd = shm_fd(size);
if( (ret.poisoned =
((ret.fd = _fd < 0 ? shm_fd(size) : _fd) == -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;
}
cow_t* cow_create(size_t size)
{
return box_value(_cow_create_unboxed(size));
return box_value(_cow_create_unboxed(-1, size));
}
cow_t* cow_create_fd(int fd, size_t size)
{
fd = dup(fd);
if(__builtin_expect(fd < 0, false)) return NULL;
return box_value(_cow_create_unboxed(fd, 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))
@ -112,16 +120,27 @@ 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)) {
_cow_set_err(COW_ERR_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;

@ -2,16 +2,20 @@
#include <utility>
#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; }
~_inner();
_inner(size_t sz);
_inner(int fd, size_t sz);
_inner(cow_t* ptr);
_inner(const _inner& copy) = delete;
@ -19,15 +23,25 @@ struct Cow::_inner {
_inner() = delete;
};
Cow::_inner::~_inner() {
if(UNLIKELY(cow.poisoned)) return;
_cow_free_unboxed(ptr());
cow.poisoned=true;
}
Cow::_inner::_inner(int fd, size_t sz) : cow(_cow_create_unboxed(fd, sz)){
if(UNLIKELY(cow.poisoned)) throw CowException(cow_err());
}
Cow::_inner::_inner(size_t sz) : cow(_cow_create_unboxed(sz)){}
Cow::_inner::_inner(size_t sz) : _inner(-1, sz) {}
Cow::_inner::_inner(cow_t* ptr) : cow(*ptr)
{
free(ptr);
if(UNLIKELY(cow.poisoned)) throw CowException(cow_err());
}
Cow::Cow(size_t size) : super(std::make_shared<_inner>(size)){}
Cow::Cow(int fd, size_t size) : super(std::make_shared<_inner>(fd, size)){}
Cow::Cow(cow_t* raw) : super(std::make_shared<_inner>(raw)){}
Cow::Cow(Cow&& m) : super(std::move(*const_cast<std::shared_ptr<_inner>*>(&m.super))){}
@ -37,11 +51,11 @@ Cow::~Cow(){}
Cow Cow::from_raw(cow_t* owned) { if(cow_is_fake(owned)) throw "Trying to create real from fake raw"; else return Cow(owned); }
Cow::Fake Cow::clone() const { return Fake::from_real(*this); }
cow_t* Cow::get_raw() const { return super->ptr(); }
cow_t* Cow::get_raw() const { return super ? super->ptr() : nullptr; }
size_t Cow::size() const { return super->cow.size; }
size_t Cow::size() const { return super ? super->cow.size : 0; }
cow_t* Cow::raw() const { return &super->cow; }
cow_t* Cow::raw() const { return super ? &super->cow : nullptr; }
Cow::Fake::Fake(const Cow& copy) : Cow(copy), fake(cow_clone(copy.super->ptr())){}
Cow::Fake::Fake(const Fake& copy) : Cow(copy), fake(cow_clone(copy.fake)){}//Fake(*static_cast<const Cow*>(&copy)){}
@ -56,4 +70,10 @@ Cow::Fake Cow::Fake::Fake::from_real(const Cow& real) { return Fake(real); }
Cow::Fake Cow::Fake::clone() const { return Fake(*static_cast<const Fake*>(this)); }
cow_t* Cow::Fake::get_raw() const { return fake; }
// Error
const char* CowException::what() const noexcept {
auto str = cow_err_msg(kind);
if(str && *str) return *str;
else return "Unknown error";
}

@ -2,22 +2,30 @@
#ifndef _COW_T_H
#define _COW_T_H
#define internal __attribute__((visibility("internal")))
#ifdef __cplusplus
#define restrict __restrict__
extern "C" {
#else
#include <stdbool.h>
#endif
#include <stdlib.h>
#include "macros.h"
#include <cow.h>
#include "error.h"
struct cow_mapped_slice {
void* origin; // ptr to mapped memory. This *MUST* be the first field and have an offset of 0.
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
@ -36,7 +44,7 @@ _Static_assert
(offsetof(cow_t, size) == sizeof(void*), "`cow_t.size` should have an offset equal to `sizeof(void*)` or cow_size_unsafe() becomes UB.");
#endif
cow_t _cow_create_unboxed(size_t size) internal;
cow_t _cow_create_unboxed(int rfd, size_t size) internal;
void _cow_free_unboxed(const cow_t* cow) internal;
#ifdef __cplusplus

@ -0,0 +1,3 @@
#include "error.h"
_Thread_local internal enum cow_err_kind _cow_last_error = COW_ERR_NONE;

@ -0,0 +1,47 @@
#include <array>
#include <cow.h>
#include "error.h"
namespace _cow_error {
constexpr const size_t SIZE = (size_t)_COW_ERR_SIZE;
consteval inline void setmsg(std::array<const char*, SIZE>& ar, enum cow_err_kind kind, const char* msg)
{
ar[kind] = msg;
}
consteval inline std::array<const char*, SIZE> gen_msg_table()
{
std::array<const char*, SIZE> 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())");
setmsg(ret, COW_ERR_POISONED, "trying to use a poisoned cow");
return ret;
}
const std::array<const char*, SIZE> _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];
}
}

@ -0,0 +1,29 @@
#ifndef _COW_ERROR_H
#define _COW_ERROR_H
#include <stdlib.h>
#include <cow.h>
#include "macros.h"
#ifdef __cplusplus
extern "C" {
#define _Thread_local thread_local
#define restrict __restrict__
#endif
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 */

@ -0,0 +1,26 @@
#ifndef internal
#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, "<libcow> [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)

@ -8,6 +8,7 @@
#include <vector>
#include <cow/area.hpp>
#include <cow/typed.hpp>
using namespace _cow_util;
@ -146,7 +147,7 @@ namespace Tiling {
template<typename T = unsigned char>
void print_slice(Slice<T> memory)
{
printf("slice: { %p, %lu (%lu bytes) }\n", memory.area(), memory.size(), memory.size_bytes());
printf("slice: { %p, %lu (%lu bytes) }\n", memory.ptr(), memory.size(), memory.size_bytes());
}
@ -168,6 +169,12 @@ void moving_cow(Cow moved)
read_fake(moved);
}
void typed_cow()
{
TypedCow<int> tc(1024);
}
int main()
{
Cow _area(4000);
@ -203,7 +210,7 @@ int main()
Cow::Fake clone = real;
printf("Fake size: %lu\n", clone.size());
printf("Fake ptr: %p\n", clone.area());
printf("Fake ptr: %p\n", clone.ptr());
read_fake(clone);
write_fake(clone, "hello fake!");
@ -216,5 +223,14 @@ int main()
printf("First byte of: fake = %x\n", clone[0]);
read_fake(clone); //clone still functions because of refcount on origin.
typed_cow();
printf("Last error: %d, %s\n", cow_err(), *cow_err_msg(cow_err()));
try {
Cow should_fail(SIZE_MAX);
Cow::Fake should_fail_clone = should_fail;
} catch (CowException& cw) {
printf("Error! (%d) %s\n", cw.kind, cw.what());
}
printf("Last error: %d, %s\n", cow_err(), *cow_err_msg(cow_err()));
return 0;
}

Loading…
Cancel
Save