Zix

Zix is a lightweight C library of portability wrappers and data structures.

1 Using Zix

1.1 Error Handling

Most functions return a ZixStatus which describes whether they succeeded (with zero, or ZIX_STATUS_SUCCESS) or why they failed. The human-readable message for a status can be retrieved with zix_strerror().

1.2 String Views

For performance reasons, many functions in zix that take a string take a ZixStringView, rather than a bare pointer. This forces code to be explicit about string measurement, which discourages common patterns of repeated measurement of the same string. For convenience, several macros and functions are provided for constructing string views:

ZIX_STATIC_STRING

Initializes a string view from a string literal. Note that this may only be used with inlined string literals, passing a pointer to an arbitrary string is undefined behaviour, for example:

static const ZixStringView hello = ZIX_STATIC_STRING("hello");

zix_empty_string()

Constructs a view of an empty string, for example:

ZixStringView empty = zix_empty_string();

zix_string()

Constructs a view of an arbitrary string, for example:

ZixStringView view = zix_string(string_pointer);

This calls strlen to measure the string. Modern compilers should optimize this away if the parameter is a literal.

zix_substring()

Constructs a view of a slice of a string with an explicit length, for example:

ZixStringView slice = zix_substring(string_pointer, 4);

This can also be used to create a view of a pre-measured string. If the length a dynamic string is already known, this is faster than zix_string(), since it avoids redundant measurement.

These constructors can be used inline when passing parameters, but if the same dynamic string is used several times, it is better to make a string view variable to avoid redundant measurement.

1.3 Allocation

Functions or objects that allocate memory take a pointer to a ZixAllocator, which can be used to customize the allocation scheme. To simply use the system allocator, callers can pass NULL, which is equivalent to zix_default_allocator().

Zix includes one implementation of a custom allocator, a very simple bump-pointer allocator which can be created with zix_bump_allocator().

A convenience API for using custom allocators (or the system allocator as a fallback) is provided with functions like zix_malloc() that reflect the standard C API.

Memory allocated with an allocator must be freed by calling zix_free() with the same allocator.

2 Zix C API

2.1 Utilities

2.1.1 Attributes

ZIX_BEGIN_DECLS

Begin public API definitions.

ZIX_END_DECLS

End public API definitions.

ZIX_ALWAYS_INLINE_FUNC

Should absolutely always be inlined.

ZIX_PURE_FUNC

Only reads memory.

ZIX_CONST_FUNC

Only reads its parameters.

ZIX_MALLOC_FUNC

Allocates memory with no pointers in it.

ZIX_NODISCARD

Returns a value that must be used.

ZIX_PURE_API

A pure function in the public API that only reads memory.

ZIX_CONST_API

A const function in the public API that is pure and only reads parameters.

ZIX_MALLOC_API

A malloc function in the public API that returns allocated memory.

ZIX_LOG_FUNC(fmt, arg1)

A function with printf-like parameters.

ZIX_UNUSED(name)

An unused parameter.

ZIX_NONNULL

A non-null pointer.

ZIX_NULLABLE

A nullable pointer.

ZIX_ALLOCATED

An allocated (possibly null) pointer.

ZIX_UNSPECIFIED

A pointer with unspecified nullability.

2.1.2 Status Codes

enum ZixStatus

A status code returned by functions.

enumerator ZIX_STATUS_SUCCESS

Success.

enumerator ZIX_STATUS_ERROR

Unknown error.

enumerator ZIX_STATUS_NO_MEM

Out of memory.

enumerator ZIX_STATUS_NOT_FOUND

Not found.

enumerator ZIX_STATUS_EXISTS

Exists.

enumerator ZIX_STATUS_BAD_ARG

Bad argument.

enumerator ZIX_STATUS_BAD_PERMS

Bad permissions.

enumerator ZIX_STATUS_REACHED_END

Reached end.

enumerator ZIX_STATUS_TIMEOUT

Timeout.

enumerator ZIX_STATUS_OVERFLOW

Overflow.

enumerator ZIX_STATUS_NOT_SUPPORTED

Not supported.

enumerator ZIX_STATUS_UNAVAILABLE

Resource unavailable.

enumerator ZIX_STATUS_NO_SPACE

Out of storage space.

Too many links.

const char *zix_strerror(ZixStatus status)

Return a string describing a status code in plain English.

The returned string is always one sentence, with an uppercase first character, and no trailing period.

2.1.3 String View

struct ZixStringView

An immutable slice of a string.

This type is used for many string parameters, to allow referring to slices of strings in-place and to avoid redundant string measurement.

const char *data

Pointer to the first character.

size_t length

Length of string in bytes.

ZixStringView zix_empty_string(void)

Return a view of an empty string.

ZixStringView zix_substring(const char *const str, const size_t len)

Return a view of a substring, or a premeasured string.

This makes either a view of a slice of a string (which may not be null terminated), or a view of a string that has already been measured. This is faster than zix_string() for dynamic strings since it does not call strlen, so should be used when the length of the string is already known.

Parameters:
  • str – Pointer to the start of the substring.

  • len – Length of the substring in bytes, not including the trailing null terminator if present.

ZixStringView zix_string(const char *const str)

Return a view of an entire string by measuring it.

This makes a view of the given string by measuring it with strlen.

Parameters:
  • str – Pointer to the start of a null-terminated C string, or null.

char *zix_string_view_copy(ZixAllocator *allocator, ZixStringView view)

Copy a string view into a newly allocated null-terminated string.

bool zix_string_view_equals(ZixStringView lhs, ZixStringView rhs)

Return true if both string views refer to equal strings.

This may be significantly faster than a full string comparison, because it has fast paths for when the operands have different lengths, or point to the same string data.

ZIX_STATIC_STRING(s)

Initialize a string view from a string literal.

2.2 Allocation

2.2.1 Allocator

struct ZixAllocatorImpl

Definition of ZixAllocator.

ZixMallocFunc malloc

Allocate.

ZixCallocFunc calloc

Allocate and zero.

ZixReallocFunc realloc

Reallocate.

ZixFreeFunc free

Free.

ZixAlignedAllocFunc aligned_alloc

Allocate aligned.

ZixAlignedFreeFunc aligned_free

Free aligned.

typedef struct ZixAllocatorImpl ZixAllocator

A memory allocator.

This object-like structure provides an interface like the standard C functions malloc(), calloc(), realloc(), free(), and aligned_alloc(). It contains function pointers that differ from their standard counterparts by taking a context parameter (a pointer to this struct), which allows the user to implement custom stateful allocators.

typedef void *(*ZixMallocFunc)(ZixAllocator *allocator, size_t size)

General malloc-like memory allocation function.

This works like the standard C malloc(), except has an additional handle parameter for implementing stateful allocators without static data.

typedef void *(*ZixCallocFunc)(ZixAllocator *allocator, size_t nmemb, size_t size)

General calloc-like memory allocation function.

This works like the standard C calloc(), except has an additional handle parameter for implementing stateful allocators without static data.

typedef void *(*ZixReallocFunc)(ZixAllocator *allocator, void *ptr, size_t size)

General realloc-like memory reallocation function.

This works like the standard C remalloc(), except has an additional handle parameter for implementing stateful allocators without static data.

typedef void (*ZixFreeFunc)(ZixAllocator *allocator, void *ptr)

General free-like memory deallocation function.

This works like the standard C remalloc(), except has an additional handle parameter for implementing stateful allocators without static data.

typedef void *(*ZixAlignedAllocFunc)(ZixAllocator *allocator, size_t alignment, size_t size)

General aligned_alloc-like memory deallocation function.

This works like the standard C aligned_alloc(), except has an additional handle parameter for implementing stateful allocators without static data.

typedef void (*ZixAlignedFreeFunc)(ZixAllocator *allocator, void *ptr)

General aligned memory deallocation function.

This works like the standard C free(), but must be used to free memory allocated with the aligned_alloc() method of the allocator. This allows portability to systems (like Windows) that can’t use the same free function in these cases.

ZixAllocator *zix_default_allocator(void)

Return the default allocator which simply uses the system allocator.

void *zix_malloc(ZixAllocator *const allocator, const size_t size)

Convenience wrapper that defers to malloc() if allocator is null.

void *zix_calloc(ZixAllocator *const allocator, const size_t nmemb, const size_t size)

Convenience wrapper that defers to calloc() if allocator is null.

void *zix_realloc(ZixAllocator *const allocator, void *const ptr, const size_t size)

Convenience wrapper that defers to realloc() if allocator is null.

void zix_free(ZixAllocator *const allocator, void *const ptr)

Convenience wrapper that defers to free() if allocator is null.

void *zix_aligned_alloc(ZixAllocator *const allocator, const size_t alignment, const size_t size)

Convenience wrapper that defers to the system allocator if allocator is null.

void zix_aligned_free(ZixAllocator *const allocator, void *const ptr)

Convenience wrapper that defers to the system allocator if allocator is null.

2.2.2 Bump Allocator

struct ZixBumpAllocator

A simple bump-pointer allocator that never frees.

This is about the simplest possible allocator that is useful in practice. It uses a user-provided memory buffer with a fixed size, and allocates by simply “bumping” the top offset like a stack. This approach is simple, extremely fast, and hard real-time safe, but at the cost of being limited to narrow use cases since there is (almost) no support for deallocation.

Using this allocator requires knowing up-front the total amount of memory that will be allocated (without reuse). Typically this makes sense in short scopes like a function call.

This allocator adheres to standard C semantics as much as possible, but is much more restrictive. Specifically:

  • All allocations are aligned to sizeof(uintmax_t).

  • Both free() and realloc() only work on the most recently allocated pointer, essentially serving as a cheap pop and pop-push, respectively.

  • Calling free() means that realloc() will fail and free() will do nothing until the next allocation. In other words, free() can’t be used twice in a row.

  • There is no relocation: realloc() always returns either the input pointer, or null.

ZixAllocator base

Base allocator instance.

void *buffer

User-owned memory buffer.

size_t last

Last allocation offset in bytes.

size_t top

Stack top/end offset in bytes.

size_t capacity

Size of buffer in bytes (the maximum top)

ZixBumpAllocator zix_bump_allocator(size_t capacity, void *buffer)

Return a bump allocator that works within a provided buffer.

2.3 Algorithms

2.3.1 Digest

Functions to generate a short “digest” of data with minimal collisions.

These are good general-purpose hash functions for indexing arbitrary data, but are not necessarily stable across platforms and should never be used for cryptographic purposes.

uint32_t zix_digest32(uint32_t seed, const void *buf, size_t len)

Return a 32-bit hash of a buffer.

This can be used for any size or alignment.

uint32_t zix_digest32_aligned(uint32_t seed, const void *buf, size_t len)

Return a 32-bit hash of an aligned buffer.

Both the buffer and size must be aligned to 32 bits. For data that fits these requirements, this is equivalent to, but faster than, zix_digest32().

uint64_t zix_digest64(uint64_t seed, const void *buf, size_t len)

Return a 64-bit hash of a buffer.

This can be used for any size or alignment.

uint64_t zix_digest64_aligned(uint64_t seed, const void *buf, size_t len)

Return a 64-bit hash of an aligned buffer.

Both the buffer and size must be aligned to 64 bits. For data that fits these requirements, this is equivalent to, but faster than, zix_digest64().

size_t zix_digest(size_t seed, const void *buf, size_t len)

Return a pointer-sized hash of a buffer.

This can be used for any size or alignment.

Internally, this simply dispatches to zix_digest32() or zix_digest64() as appropriate.

size_t zix_digest_aligned(size_t seed, const void *buf, size_t len)

Return a pointer-sized hash of an aligned buffer.

Both the buffer and size must be aligned to the pointer size. For data that fits these requirements, this is equivalent to, but faster than, zix_digest().

Internally, this simply dispatches to zix_digest32_aligned() or zix_digest64_aligned() as appropriate.

2.4 Data Structures

2.4.1 BTree

2.4.1.1 Setup
typedef struct ZixBTreeImpl ZixBTree

A B-Tree.

typedef int (*ZixBTreeCompareFunc)(const void *a, const void *b, const void *user_data)

Function for comparing two B-Tree elements.

typedef void (*ZixBTreeDestroyFunc)(void *ptr, const void *user_data)

Function to destroy a B-Tree element.

ZixBTree *zix_btree_new(ZixAllocator *allocator, ZixBTreeCompareFunc cmp, const void *cmp_data)

Create a new (empty) B-Tree.

The given comparator must be a total ordering and is used to internally organize the tree and look for values exactly.

Searching can be done with a custom comparator that supports wildcards, see zix_btree_lower_bound() for details.

void zix_btree_free(ZixBTree *t, ZixBTreeDestroyFunc destroy, const void *destroy_data)

Free t and all the nodes it contains.

Parameters:
  • t – The tree to free.

  • destroy – Function to call once for every value in the tree. This can be used to free values if they are dynamically allocated.

  • destroy_data – Opaque user data pointer to pass to destroy.

void zix_btree_clear(ZixBTree *t, ZixBTreeDestroyFunc destroy, const void *destroy_data)

Clear everything from t, leaving it empty.

Parameters:
  • t – The tree to clear.

  • destroy – Function called exactly once for every value in the tree, just before that value is removed from the tree.

  • destroy_data – Opaque user data pointer to pass to destroy.

size_t zix_btree_size(const ZixBTree *t)

Return the number of elements in t

ZIX_BTREE_MAX_HEIGHT

The maximum height of a ZixBTree.

This is exposed because it determines the size of iterators, which are statically sized so they can used on the stack. The usual degree (or “fanout”) of a B-Tree is high enough that a relatively short tree can contain many elements. With the default page size of 4 KiB, the default height of 6 is enough to store trillions.

2.4.1.2 Iteration
struct ZixBTreeIter

An iterator over a B-Tree.

Note that modifying the tree invalidates all iterators.

The contents of this type are considered an implementation detail and should not be used directly by clients. They are nevertheless exposed here so that iterators can be allocated on the stack.

ZixBTreeNode *nodes[ZIX_BTREE_MAX_HEIGHT]

Node stack.

uint16_t indexes[ZIX_BTREE_MAX_HEIGHT]

Index stack.

uint16_t level

Current level.

typedef struct ZixBTreeNodeImpl ZixBTreeNode

An opaque node in a B-Tree.

const ZixBTreeIter zix_btree_end_iter

A static end iterator for convenience.

void *zix_btree_get(ZixBTreeIter ti)

Return the data at the given position in the tree.

ZixBTreeIter zix_btree_begin(const ZixBTree *t)

Return an iterator to the first (smallest) element in t

ZixBTreeIter zix_btree_end(const ZixBTree *t)

Return an iterator to the end of t (one past the last element)

bool zix_btree_iter_equals(ZixBTreeIter lhs, ZixBTreeIter rhs)

Return true iff lhs is equal to rhs

bool zix_btree_iter_is_end(const ZixBTreeIter i)

Return true iff i is an iterator at the end of a tree.

ZixStatus zix_btree_iter_increment(ZixBTreeIter *i)

Increment i to point to the next element in the tree.

ZixBTreeIter zix_btree_iter_next(ZixBTreeIter iter)

Return an iterator one past iter

2.4.1.3 Modification
ZixStatus zix_btree_insert(ZixBTree *t, void *e)

Insert the element e into t.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS on success, ZixStatus.ZIX_STATUS_EXISTS, or ZixStatus.ZIX_STATUS_NO_MEM.

ZixStatus zix_btree_remove(ZixBTree *t, const void *e, void **out, ZixBTreeIter *next)

Remove the element e from t.

Parameters:
  • t – Tree to remove from.

  • e – Value to remove.

  • out – Set to point to the removed pointer (which may not equal e).

  • next – On successful return, set to point at element immediately following e.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS on success, or ZixStatus.ZIX_STATUS_NOT_FOUND.

2.4.1.4 Searching
ZixStatus zix_btree_find(const ZixBTree *t, const void *e, ZixBTreeIter *ti)

Set ti to an element exactly equal to e in t.

If no such item exists, ti is set to the end.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS on success, or ZixStatus.ZIX_STATUS_NOT_FOUND.

ZixStatus zix_btree_lower_bound(const ZixBTree *t, ZixBTreeCompareFunc compare_key, const void *compare_key_data, const void *key, ZixBTreeIter *ti)

Set ti to the smallest element in t that is not less than e.

The given comparator must be compatible with the tree comparator, that is, any two values must have the same ordering according to both. Within this constraint, it may implement fuzzier searching by handling special search key values, for example with wildcards.

If the search key e compares equal to many values in the tree, then ti will be set to the least such element.

The comparator is always called with an actual value in the tree as the first argument, and key as the second argument.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS.

2.4.2 Hash

2.4.2.1 Datatypes
typedef void ZixHashKey

The type of a key within a record.

typedef void ZixHashRecord

The type of a hash table record.

typedef void ZixHashSearchData

User data for key comparison function.

typedef struct ZixHashImpl ZixHash

A hash table.

This is an open addressing hash table that stores pointers to arbitrary user data. Internally, everything is stored in a single flat array that is resized when necessary.

The single user-provided pointer that is stored in the table is called a “record”. A record contains a “key”, which is accessed via a user-provided function. This design supports storing large records (which may be expensive to allocate/construct) without requiring an entire record to search. Simple atomic values can be stored by providing a trivial identity function as a key function.

The table uses power of 2 sizes with a growth factor of 2, so that hash values can be folded into an array index using bitwise AND as a fast modulo. This means that only the necessary low bits of the hash value will be used, so the hash function must be well-balanced within this range. More or less any good modern hash algorithm will be fine, but beware, for example, hash functions that assume they are targeting a table with a prime size.

Since this doubles and halves in size, it may not be an optimal choice if memory reuse is a priority. A growth factor of 1.5 with fast range reduction may be a better choice there, at the cost of requiring 128-bit arithmetic on 64-bit platforms, and indexing operations being slightly more expensive.

typedef size_t ZixHashCode

A full hash code for a key which is not folded down to the table size.

2.4.2.2 Setup
typedef const ZixHashKey *(*ZixKeyFunc)(const ZixHashRecord *record)

User function for getting the key of a record.

typedef ZixHashCode (*ZixHashFunc)(const ZixHashKey *key)

User function for computing the hash of a key.

typedef bool (*ZixKeyEqualFunc)(const ZixHashKey *a, const ZixHashKey *b)

User function for determining if two keys are truly equal.

ZixHash *zix_hash_new(ZixAllocator *allocator, ZixKeyFunc key_func, ZixHashFunc hash_func, ZixKeyEqualFunc equal_func)

Create a new hash table.

Parameters:
  • allocator – Allocator used for the internal array.

  • key_func – A function to retrieve the key from a record.

  • hash_func – The key hashing function.

  • equal_func – A function to test keys for equality.

void zix_hash_free(ZixHash *hash)

Free hash

size_t zix_hash_size(const ZixHash *hash)

Return the number of elements in the hash.

2.4.2.3 Iteration
typedef size_t ZixHashIter

An iterator to an entry in a hash table.

This is really just an index, but should be considered opaque to the user and only used via the provided API and equality comparison.

ZixHashIter zix_hash_begin(const ZixHash *hash)

Return an iterator to the first record in a hash, or the end if it is empty.

ZixHashIter zix_hash_end(const ZixHash *hash)

Return an iterator one past the last possible record in a hash.

ZixHashRecord *zix_hash_get(const ZixHash *hash, ZixHashIter i)

Return the record pointed to by an iterator.

ZixHashIter zix_hash_next(const ZixHash *hash, ZixHashIter i)

Return an iterator that has been advanced to the next record in a hash.

2.4.2.4 Modification
struct ZixHashInsertPlan

A “plan” (position) to insert a record in a hash table.

This contains everything necessary to insert a record, except the record itself. It is exposed to split up insertion so that records only need to be allocated if an existing record is not found, but the contents should be considered opaque to the user.

ZixHashCode code

Hash code used to find this position.

ZixHashIter index

Index into hash table.

typedef bool (*ZixKeyMatchFunc)(const ZixHashKey *key, const ZixHashSearchData *user_data)

User function for determining if a key matches in a custom search.

ZixHashInsertPlan zix_hash_plan_insert(const ZixHash *hash, const ZixHashKey *key)

Find the best position to insert a record with the given key.

This searches the hash table and returns either the position of an existing equivalent record, or the best available position to insert a new one. The difference can be determined with zix_hash_record_at().

If the returned position is not occupied, then it is valid to insert a record with this key using zix_hash_insert_at() until the hash table is modified (which invalidates the position).

ZixHashInsertPlan zix_hash_plan_insert_prehashed(const ZixHash *hash, ZixHashCode code, ZixKeyMatchFunc predicate, const ZixHashSearchData *user_data)

Find the best position to insert a record with a custom search.

This is an advanced low-level version of zix_hash_plan_insert(). It allows a precalculated hash code to be given, along with a custom search predicate. These must be compatible with the functions that manage the hash table: trivial usage would be to pass the equality function used by the hash and the key to search for.

This is exposed to make it possible to avoid constructing a key at all when potentially inserting. For example, if keys are structures with a fixed header and a variably-sized body, and you have a separate header and body this can be used to find an insert position without having to allocate a contiguous key.

Note that care must be taken when using this function: improper use can corrupt the hash table. The hash code given must be correct for the key to be inserted, and the predicate must return true only if the key it is called with (the first argument) matches the key to be inserted.

ZixHashRecord *zix_hash_record_at(const ZixHash *hash, ZixHashInsertPlan position)

Return the record at the given position, or null.

This is used to determine if a position returned from zix_hash_plan_insert() can be used to insert a new record, or to access the existing matching record.

ZixStatus zix_hash_insert_at(ZixHash *hash, ZixHashInsertPlan position, ZixHashRecord *record)

Insert a record at a specific position.

The position must have been found with an earlier call to zix_hash_plan_insert(), and no modifications must have been made to the hash table since.

Parameters:
  • hash – The hash table.

  • position – The position for the new record.

  • record – The record to insert which, on success, can now be considered owned by the hash table.

Returns:

ZIX_STATUS_SUCCESS, ZIX_STATUS_EXISTS if a record already exists at this position, or ZIX_STATUS_NO_MEM if growing the hash table failed.

ZixStatus zix_hash_insert(ZixHash *hash, ZixHashRecord *record)

Insert a record.

This is a trivial wrapper for zix_hash_plan_insert() and zix_hash_insert_at() that is more convenient when record construction is not expensive.

Parameters:
  • hash – The hash table.

  • record – The record to insert which, on success, can now be considered owned by the hash table.

Returns:

ZIX_STATUS_SUCCESS, ZIX_STATUS_EXISTS, or ZIX_STATUS_NO_MEM.

ZixStatus zix_hash_erase(ZixHash *hash, ZixHashIter i, ZixHashRecord **removed)

Erase a record at a specific position.

Parameters:
  • hash – The hash table to remove the record from.

  • i – Iterator to the record to remove. This must be a valid iterator from an earlier call to zix_hash_find(), that is, the hash table must not have been modified since.

  • removed – Set to the removed record, or null.

Returns:

ZIX_STATUS_SUCCES or ZIX_STATUS_BAD_ARG if i does not point at a removable record.

ZixStatus zix_hash_remove(ZixHash *hash, const ZixHashKey *key, ZixHashRecord **removed)

Remove a record.

Parameters:
  • hash – The hash table.

  • key – The key of the record to remove.

  • removed – Set to the removed record, or null.

Returns:

ZIX_STATUS_SUCCES or ZIX_STATUS_NOT_FOUND.

2.4.2.5 Searching
ZixHashIter zix_hash_find(const ZixHash *hash, const ZixHashKey *key)

Find the position of a record with a given key.

Parameters:
  • hash – The hash table to search.

  • key – The key of the desired record.

Returns:

An iterator to the matching record, or the end iterator if no such record exists.

ZixHashRecord *zix_hash_find_record(const ZixHash *hash, const ZixHashKey *key)

Find a record with a given key.

This is essentially the same as zix_hash_find(), but returns a pointer to the record for convenience.

Parameters:
  • hash – The hash table to search.

  • key – The key of the desired record.

Returns:

A pointer to the matching record, of null if no such record exists.

2.4.3 Ring

2.4.3.1 Setup
typedef struct ZixRingImpl ZixRing

A lock-free ring buffer.

Thread-safe (with a few noted exceptions) for a single reader and single writer, and realtime-safe on both ends.

ZixRing *zix_ring_new(ZixAllocator *allocator, uint32_t size)

Create a new ring.

Parameters:
  • allocator – Allocator for the ring object and its array.

  • size – Minimum size of the ring in bytes (rounded up to a power of 2).

Note that one byte of the ring is reserved, so in order to be able to write n bytes to the ring at once, size must be n + 1.

void zix_ring_free(ZixRing *ring)

Destroy a ring.

This frees the ring structure and its buffer, discarding its contents.

ZixStatus zix_ring_mlock(ZixRing *ring)

Lock the ring data into physical memory.

This function is NOT thread safe or real-time safe, but it should be called after zix_ring_new() to lock all ring memory to avoid page faults while using the ring.

void zix_ring_reset(ZixRing *ring)

Reset (empty) a ring.

This function is NOT thread-safe, it may only be called when there is no reader or writer.

uint32_t zix_ring_capacity(const ZixRing *ring)

Return the capacity (the total write space when empty).

This function returns a constant for any given ring, and may (but usually shouldn’t) be called anywhere.

2.4.3.2 Reading

Functions that may only be called by the read thread.

uint32_t zix_ring_read_space(const ZixRing *ring)

Return the number of bytes available for reading.

This function returns at most one less than the ring’s buffer size.

uint32_t zix_ring_peek(ZixRing *ring, void *dst, uint32_t size)

Read from the ring without advancing the read head.

Parameters:
  • ring – The ring to read data from.

  • dst – The buffer to write data to.

  • size – The number of bytes to read from ring and write to dst.

Returns:

The number of bytes read, which is either size on success, or zero on failure.

uint32_t zix_ring_read(ZixRing *ring, void *dst, uint32_t size)

Read from the ring and advance the read head.

Parameters:
  • ring – The ring to read data from.

  • dst – The buffer to write data to.

  • size – The number of bytes to read from ring and write to dst.

Returns:

The number of bytes read, which is either size on success, or zero on failure.

uint32_t zix_ring_skip(ZixRing *ring, uint32_t size)

Advance the read head, ignoring any data.

Returns:

Either size on success, or zero if there aren’t enough bytes to skip.

2.4.3.3 Writing

Functions that may only be called by the write thread.

struct ZixRingTransaction

A transaction for writing data in multiple parts.

The simple zix_ring_write() can be used to write an atomic message that will immediately be visible to the reader, but transactions allow data to be written in several chunks before being “committed” and becoming readable. This can be useful for things like prefixing messages with a header without needing an allocated buffer to construct the “packet”.

The contents of this structure are an implementation detail and must not be manipulated by the user.

uint32_t read_head

Read head at the start of the transaction.

uint32_t write_head

Write head if the transaction were committed.

uint32_t zix_ring_write_space(const ZixRing *ring)

Return the number of bytes available for writing.

This function returns at most one less than the ring’s buffer size.

uint32_t zix_ring_write(ZixRing *ring, const void *src, uint32_t size)

Write data to the ring.

This writes a contiguous input buffer of bytes to the ring.

Parameters:
  • ring – The ring to write data to.

  • src – The buffer to read data from.

  • size – The number of bytes to read from src and write to ring.

Returns:

The number of bytes written, which is either size on success, or zero on failure.

ZixRingTransaction zix_ring_begin_write(ZixRing *ring)

Begin a write.

The returned transaction is initially empty. Data can be written to it by calling zix_ring_amend_write() one or more times, then finishing with zix_ring_commit_write().

Note that the returned “transaction” is not meant to be long-lived: a call to this function should be (more or less) immediately followed by calls to zix_ring_amend_write() then a call to zix_ring_commit_write().

Parameters:
  • ring – The ring to write data to.

Returns:

A new empty transaction.

ZixStatus zix_ring_amend_write(ZixRing *ring, ZixRingTransaction *tx, const void *src, uint32_t size)

Amend the current write with some data.

The data is written immediately after the previously amended data, as if they were written contiguously with a single write call. This data is not visible to the reader until zix_ring_commit_write() is called.

If any call to this function returns an error, then the transaction is invalid and must not be committed. No cleanup is necessary for an invalid transaction. Any bytes written while attempting the transaction will remain in the free portion of the buffer and be overwritten by subsequent writes.

Parameters:
  • ring – The ring this transaction is writing to.

  • tx – The active transaction, from zix_ring_begin_write().

  • src – Pointer to the data to write.

  • size – Length of data to write in bytes.

Returns:

ZixStatus.ZIX_STATUS_NO_MEM or ZixStatus.ZIX_STATUS_SUCCESS.

ZixStatus zix_ring_commit_write(ZixRing *ring, const ZixRingTransaction *tx)

Commit the current write.

This atomically updates the state of the ring, so that the reader will observe the data written during the transaction.

This function usually shouldn’t be called for any transaction which zix_ring_amend_write() returned an error for.

Parameters:
  • ring – The ring this transaction is writing to.

  • tx – The active transaction, from zix_ring_begin_write().

Returns:

ZixStatus.ZIX_STATUS_SUCCESS.

2.4.4 Tree

2.4.4.1 Setup
typedef struct ZixTreeImpl ZixTree

A balanced binary search tree.

typedef int (*ZixTreeCompareFunc)(const void *a, const void *b, const void *user_data)

Function for comparing two Tree elements.

typedef void (*ZixTreeDestroyFunc)(void *ptr, const void *user_data)

Function to destroy a Tree element.

ZixTree *zix_tree_new(ZixAllocator *allocator, bool allow_duplicates, ZixTreeCompareFunc cmp, void *cmp_data, ZixTreeDestroyFunc destroy, const void *destroy_user_data)

Create a new (empty) tree.

void zix_tree_free(ZixTree *t)

Free t

size_t zix_tree_size(const ZixTree *t)

Return the number of elements in t

2.4.4.2 Iteration
typedef struct ZixTreeNodeImpl ZixTreeIter

An iterator over a ZixTree.

void *zix_tree_get(const ZixTreeIter *ti)

Return the data associated with the given tree item.

ZixTreeIter *zix_tree_begin(ZixTree *t)

Return an iterator to the first (smallest) element in t

ZixTreeIter *zix_tree_end(ZixTree *t)

Return an iterator the the element one past the last element in t

bool zix_tree_iter_is_end(const ZixTreeIter *i)

Return true iff i is an iterator to the end of its tree.

ZixTreeIter *zix_tree_rbegin(ZixTree *t)

Return an iterator to the last (largest) element in t

ZixTreeIter *zix_tree_rend(ZixTree *t)

Return an iterator the the element one before the first element in t

bool zix_tree_iter_is_rend(const ZixTreeIter *i)

Return true iff i is an iterator to the reverse end of its tree.

ZixTreeIter *zix_tree_iter_next(ZixTreeIter *i)

Return an iterator that points to the element one past i

ZixTreeIter *zix_tree_iter_prev(ZixTreeIter *i)

Return an iterator that points to the element one before i

2.4.4.3 Modification
ZixStatus zix_tree_insert(ZixTree *t, void *e, ZixTreeIter **ti)

Insert the element e into t and point ti at the new element.

ZixStatus zix_tree_remove(ZixTree *t, ZixTreeIter *ti)

Remove the item pointed at by ti from t

2.4.4.4 Searching
ZixStatus zix_tree_find(const ZixTree *t, const void *e, ZixTreeIter **ti)

Set ti to an element equal to e in t.

If no such item exists, ti is set to NULL.

2.5 Threading

2.5.1 Semaphore

typedef struct ZixSemImpl ZixSem

A counting semaphore.

This is an integer that is never negative, and has two main operations: increment (post) and decrement (wait). If a decrement can’t be performed (because the value is 0) the caller will be blocked until another thread posts and the operation can succeed.

Semaphores can be created with any starting value, but typically this will be 0 so the semaphore can be used as a simple signal where each post corresponds to one wait.

Semaphores are very efficient (much moreso than a mutex/cond pair). In particular, at least on Linux, post is async-signal-safe, which means it does not block and will not be interrupted. If you need to signal from a realtime thread, this is the most appropriate primitive to use.

ZixStatus zix_sem_init(ZixSem *sem, unsigned initial)

Create sem with the given initial value.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS, or an unlikely error.

ZixStatus zix_sem_destroy(ZixSem *sem)

Destroy sem.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS, or an error.

ZixStatus zix_sem_post(ZixSem *sem)

Increment and signal any waiters.

Realtime safe.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if sem was incremented, ZixStatus.ZIX_STATUS_OVERFLOW if the maximum possible value would have been exceeded, or ZixStatus.ZIX_STATUS_BAD_ARG if sem is invalid.

ZixStatus zix_sem_wait(ZixSem *sem)

Wait until count is > 0, then decrement.

Obviously not realtime safe.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if sem was decremented, or ZixStatus.ZIX_STATUS_BAD_ARG if sem is invalid.

ZixStatus zix_sem_try_wait(ZixSem *sem)

Non-blocking version of wait().

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if sem was decremented, ZixStatus.ZIX_STATUS_UNAVAILABLE if it was already zero, or ZixStatus.ZIX_STATUS_BAD_ARG if sem is invalid.

ZixStatus zix_sem_timed_wait(ZixSem *sem, uint32_t seconds, uint32_t nanoseconds)

Wait for an amount of time until count is > 0, then decrement if possible.

Obviously not realtime safe.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if sem was decremented, ZixStatus.ZIX_STATUS_TIMEOUT if it was still zero when the timeout was reached, ZixStatus.ZIX_STATUS_NOT_SUPPORTED if the system does not support timed waits, or ZixStatus.ZIX_STATUS_BAD_ARG if sem is invalid.

2.5.2 Thread

typedef pthread_t ZixThread

A thread.

typedef void *ZixThreadResult

Thread function return type.

typedef ZixThreadResult (*ZixThreadFunc)(void*)

A thread function.

For portability reasons, the return type varies between platforms, and the return value is ignored. Thread functions should always return ZIX_THREAD_RESULT. This allows thread functions to be used directly by the system without any wrapper overhead.

“Returning” a result, and communicating with the parent thread in general, can be done through the pointer argument.

ZixStatus zix_thread_create(ZixThread *thread, size_t stack_size, ZixThreadFunc function, void *arg)

Initialize thread to a new thread.

The thread will immediately be launched, calling function with arg as the only parameter.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS on success, or ZixStatus.ZIX_STATUS_ERROR.

ZixStatus zix_thread_join(ZixThread thread)

Join thread (block until thread exits).

Returns:

ZixStatus.ZIX_STATUS_SUCCESS on success, or ZixStatus.ZIX_STATUS_ERROR.

ZIX_THREAD_RESULT

Result returned from a thread function.

ZIX_THREAD_FUNC

Thread function attribute.

2.6 File System

2.6.1 Operations

2.6.1.1 Creation and Removal
enum ZixCopyOption

Options to control filesystem copy operations.

enumerator ZIX_COPY_OPTION_NONE

Report any error.

enumerator ZIX_COPY_OPTION_OVERWRITE_EXISTING

Replace existing file.

typedef uint32_t ZixCopyOptions

Bitwise OR of ZixCopyOptions values.

ZixStatus zix_copy_file(ZixAllocator *allocator, const char *src, const char *dst, ZixCopyOptions options)

Copy the file at path src to path dst.

If supported by the system, a lightweight copy will be made to take advantage of copy-on-write support in the filesystem. Otherwise, a simple deep copy will be made.

Parameters:
  • allocator – Allocator used for a memory block for copying if necessary.

  • src – Path to source file to copy.

  • dst – Path to destination file to create.

  • options – Options to control the kind of copy and error conditions.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if dst was successfully created, or an error.

ZixStatus zix_create_directory(const char *dir_path)

Create the directory dir_path with all available permissions.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if dir_path was successfully created, or an error.

ZixStatus zix_create_directory_like(const char *dir_path, const char *existing_path)

Create the directory dir_path with the permissions of another.

This is like zix_create_directory(), but will copy the permissions from another directory.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if dir_path was successfully created, or an error.

ZixStatus zix_create_directories(ZixAllocator *allocator, const char *dir_path)

Create the directory dir_path and any parent directories if necessary.

Parameters:
  • allocator – Allocator used for a temporary path buffer if necessary.

  • dir_path – The path to the deepest directory to create.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if all directories in dir_path were successfully created (or already existed), or an error.

Create a hard link at path link that points to path target.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS, or an error.

Create a symbolic link at path link that points to path target.

Note that portable code should use zix_create_directory_symlink() if the target is a directory, since this function won’t work for that on some systems (like Windows).

Returns:

ZixStatus.ZIX_STATUS_SUCCESS, or an error.

Create a symbolic link at path link that points to the directory target.

This is a separate function from zix_create_symlink() because some systems (like Windows) require directory symlinks to be created specially.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS, or an error.

char *zix_create_temporary_directory(ZixAllocator *allocator, const char *path_pattern)

Create a unique temporary directory at a given path pattern.

The last six characters of pattern must be “XXXXXX” and will be replaced with unique characters in the result.

Parameters:
  • allocator – Allocator used for the returned path.

  • path_pattern – A path pattern ending in “XXXXXX”.

Returns:

The path of the created directory, or null.

ZixStatus zix_remove(const char *path)

Remove the file or empty directory at path

2.6.1.2 Access
typedef void (*ZixDirEntryVisitFunc)(const char *path, const char *name, void *data)

Function for reading input bytes from a stream.

Param path:

Path to the directory being visited.

Param name:

Name of the directory entry.

Param data:

Opaque user data.

void zix_dir_for_each(const char *path, void *data, ZixDirEntryVisitFunc f)

Visit every file in the directory at path.

Parameters:
  • path – A path to a directory.

  • data – Opaque user data that is passed to f.

  • f – A function called on every entry in the directory. The path parameter is always the directory path passed to this function, the name parameter is the name of the directory entry (not its full path).

bool zix_file_equals(ZixAllocator *allocator, const char *a_path, const char *b_path)

Return whether the given paths point to files with identical contents.

Parameters:
  • allocator – Allocator used for a memory block for comparison if necessary.

  • a_path – Path to the first file to compare

  • b_path – Path to the second file to compare

Returns:

True if the two files have byte-for-byte identical contents.

2.6.1.3 Resolution
char *zix_canonical_path(ZixAllocator *allocator, const char *path)

Return path as a canonical absolute path to a “real” file.

This expands all symbolic links, relative references, and removes extra directory separators.

Since this function may return null anyway, it accepts a null parameter to allow easier chaining of path functions when only the final result is required, for example:

char* path      = zix_path_join(alloc, "/some/dir", "filename.txt");
char* canonical = zix_canonical_path(path);
if (canonical) {
  // Do something with the canonical path...
} else {
  // No canonical path for some reason, we don't care which...
}
Returns:

A new canonical version of path, or null if it doesn’t exist.

2.6.1.4 Locking
enum ZixFileLockMode

A mode for locking files.

The same mode should be used for the lock and the corresponding unlock.

enumerator ZIX_FILE_LOCK_BLOCK

Block until the operation succeeds.

enumerator ZIX_FILE_LOCK_TRY

Fail if the operation would block.

ZixStatus zix_file_lock(FILE *file, ZixFileLockMode mode)

Set an advisory exclusive lock on file.

Parameters:
  • file – Handle for open file to lock.

  • mode – Lock mode.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if the file was locked, or an error.

ZixStatus zix_file_unlock(FILE *file, ZixFileLockMode mode)

Remove an advisory exclusive lock on file.

Parameters:
  • file – Handle for open file to lock.

  • mode – Lock mode.

Returns:

ZixStatus.ZIX_STATUS_SUCCESS if the file was unlocked, or an error.

2.6.1.5 Queries
enum ZixFileType

A type of file.

Note that not all types may be supported, and the system may support additional types not enumerated here.

enumerator ZIX_FILE_TYPE_NONE

Non-existent file.

enumerator ZIX_FILE_TYPE_REGULAR

Regular file.

enumerator ZIX_FILE_TYPE_DIRECTORY

Directory.

Symbolic link.

enumerator ZIX_FILE_TYPE_BLOCK

Special block file.

enumerator ZIX_FILE_TYPE_CHARACTER

Special character file.

enumerator ZIX_FILE_TYPE_FIFO

FIFO.

enumerator ZIX_FILE_TYPE_SOCKET

Socket.

enumerator ZIX_FILE_TYPE_UNKNOWN

Existing file with unknown type.

typedef ptrdiff_t ZixFileOffset

An offset into a file or a file size in bytes.

This is signed, and may be 64 bits even on 32-bit systems.

ZixFileType zix_file_type(const char *path)

Return the type of a file or directory, resolving symlinks.

Return the type of a file or directory or symlink.

On Windows, a directory symlink (actually a “reparse point”) always appears as a directory.

ZixFileOffset zix_file_size(const char *path)

Return the size of a file.

Note that the returned value is signed and must be checked for errors. Non-negative values can be thought of as the “end” offset just past the last byte.

Returns:

A non-negative size in bytes, or -1 on error.

2.6.1.6 Environment
char *zix_current_path(ZixAllocator *allocator)

Return the current working directory.

Parameters:
  • allocator – Allocator used for the returned path.

char *zix_temp_directory_path(ZixAllocator *allocator)

Return the path to a directory suitable for making temporary files.

Parameters:
  • allocator – Allocator used for the returned path.

Returns:

A new path to a temporary directory, or null on error.

2.6.2 Paths

Functions for interpreting and manipulating paths. These functions are purely lexical and do not access any filesystem.

2.6.2.1 Concatenation
char *zix_path_join(ZixAllocator *allocator, const char *a, const char *b)

Join path a and path b with a single directory separator between them.

2.6.2.2 Lexical Transformations
char *zix_path_preferred(ZixAllocator *allocator, const char *path)

Return path with preferred directory separators.

The returned path will be a copy of path with any directory separators converted to the preferred separator (backslash on Windows, slash everywhere else).

char *zix_path_lexically_normal(ZixAllocator *allocator, const char *path)

Return path converted to normal form.

Paths in normal form have all dot segments removed and use only a single preferred separator for all separators (that is, any number of separators is replaced with a single “" on Windows, and a single “/” everwhere else).

Note that this function doesn’t access the filesystem, so won’t do anything like case normalization or symbolic link dereferencing. For that, use zix_canonical_path().

char *zix_path_lexically_relative(ZixAllocator *allocator, const char *path, const char *base)

Return path relative to base if possible.

If path is not within base, a copy is returned. Otherwise, an equivalent path relative to base is returned (which may contain up-references).

2.6.2.3 Decomposition
ZixStringView zix_path_root_name(const char *path)

Return the root name of path like “C:”, or null.

ZixStringView zix_path_root_directory(const char *path)

Return the root directory of path like “/” or “", or null.

ZixStringView zix_path_root_path(const char *path)

Return the root path of path, or null.

The universal root path (in normal form) is “/”. Root paths are their own roots, but note that the path returned by this function may be partially normalized. For example, “/” is the root of “/”, “//”, “/.”, and “/..”.

On Windows, the root may additionally be an absolute drive root like “C:", a relative drive root like “C:”, or a network root like “//Host/”.

Returns:

The newly allocated root path of path, or null if it has no root or allocation failed.

ZixStringView zix_path_relative_path(const char *path)

Return the relative path component of path without the root directory.

If the path has no relative path (because it is empty or a root path), this returns null.

ZixStringView zix_path_parent_path(const char *path)

Return the path to the directory that contains path.

The parent of a root path is itself, but note that the path returned by this function may be partially normalized. For example, “/” is the parent of “/”, “//”, “/.”, and “/..”.

If path has a trailing separator, it is treated like an empty filename in a directory. For example, the parent of “/a/” is “/a”.

If path is relative, then this returns either a relative path to the parent if possible, or null. For example, the parent of “a/b” is “a”.

Returns:

The newly allocated path to the parent of path, or null if it has no parent or allocation failed.

ZixStringView zix_path_filename(const char *path)

Return the filename component of path without any directories.

The filename is the name after the last directory separator. If the path has no filename, this returns null.

ZixStringView zix_path_stem(const char *path)

Return the stem of the filename component of path.

The “stem” is the filename without the extension, that is, everything up to the last “.” if “.” is not the first character.

ZixStringView zix_path_extension(const char *path)

Return the extension of the filename component of path.

The “extension” is everything past the last “.” in the filename, if “.” is not the first character.

2.6.2.4 Queries
bool zix_path_has_root_path(const char *path)

Return true if path has a root path like “/” or “C:".

bool zix_path_has_root_name(const char *path)

Return true if path has a root name like “C:”.

bool zix_path_has_root_directory(const char *path)

Return true if path has a root directory like “/” or “".

bool zix_path_has_relative_path(const char *path)

Return true if path has a relative path “dir/file.txt”.

bool zix_path_has_parent_path(const char *path)

Return true if path has a parent path like “dir/”.

bool zix_path_has_filename(const char *path)

Return true if path has a filename like “file.txt”.

bool zix_path_has_stem(const char *path)

Return true if path has a stem like “file”.

bool zix_path_has_extension(const char *path)

Return true if path has an extension like “.txt”.

bool zix_path_is_absolute(const char *path)

Return true if path is an absolute path.

bool zix_path_is_relative(const char *path)

Return true if path is a relative path.

2.7 Environment

2.7.1 Variable Expansion

char *zix_expand_environment_strings(ZixAllocator *allocator, const char *string)

Expand shell-style variables in a string.

On Windows, this expands environment variable references like “%USERPROFILE%”. On POSIX systems, it expands environment variable references like “$HOME”, and the special path component “~”.

Parameters:
  • allocator – Allocator used for returned string.

  • string – Input string to expand.

Returns:

A newly allocated copy of string with variables expanded, or null.