Zix 0.8.2 documentation

# 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_ALLOCATED

  Allocated pointer.

ZIX_ALLOC_COUNT_SIZE(n, s)

  Function with calloc-like parameters.

  Parameters:

    - n – 1-based index of number of elements parameter

    - s – 1-based index of element size parameter

ZIX_ALLOC_SIZE(s)

  Function with malloc-like parameters.

  Parameters:

    - s – 1-based index of size parameter

ZIX_ALWAYS_INLINE_FUNC

  Function should always be inlined.

ZIX_CONST_FUNC

  Function only reads its parameters.

ZIX_LOG_FUNC(fmt, arg1)

  Function with printf-like parameters.

  Parameters:

    - fmt – 1-based index of format string parameter

    - arg1 – 1-based index of first format argument parameter

ZIX_MALLOC_FUNC

  Function allocates pointer-free memory.

ZIX_NODISCARD

  Function return value must be used.

ZIX_NONBLOCKING

  Function doesn’t allocate or block.

ZIX_NONNULL

  Non-null pointer.

ZIX_NULLABLE

  Nullable pointer.

ZIX_PURE_FUNC

  Function only reads memory.

ZIX_REALTIME

  Function is nonblocking and constant time.

ZIX_UNSPECIFIED

  Pointer with unspecified nullability.

ZIX_UNUSED(name)

  Unused parameter.

#### 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.

  enumerator ZIX_STATUS_MAX_LINKS

    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

ZIX_STATIC_STRING(s)

  Initialize a string view from a string literal.

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_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.

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.

### 2.2 Allocation

#### 2.2.1 Allocator

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.

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 *(*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 (*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 *(*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 *(*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.

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.

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.

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

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

ZixAllocator *zix_default_allocator(void)

  Return the default allocator which simply uses the system allocator.

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

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

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

  Convenience wrapper that defers to malloc() 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.

#### 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.

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.

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_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 Types

ZIX_BTREE_MAX_HEIGHT

  6U

  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.

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.

##### 2.4.1.2 Setup

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`.

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`.

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.

size_t zix_btree_size(const ZixBTree *t)

  Return the number of elements in `t`.

##### 2.4.1.3 Iteration

typedef struct ZixBTreeNodeImpl ZixBTreeNode

  An opaque node in a B-Tree.

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.

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).

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.

bool zix_btree_iter_equals(ZixBTreeIter lhs, ZixBTreeIter rhs)

  Return true iff `lhs` is equal to `rhs`.

ZixStatus zix_btree_iter_increment(ZixBTreeIter *i)

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

bool zix_btree_iter_is_end(const ZixBTreeIter i)

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

ZixBTreeIter zix_btree_iter_next(ZixBTreeIter iter)

  Return an iterator one past `iter`.

##### 2.4.1.4 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.5 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 Types

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.

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

  User function for computing the hash of a key.

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 bool (*ZixKeyEqualFunc)(const ZixHashKey *a, const ZixHashKey *b)

  User function for determining if two keys are truly equal.

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

  User function for getting the key of a record.

##### 2.4.2.2 Setup

void zix_hash_free(ZixHash *hash)

  Free `hash`.

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.

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

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

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

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.

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_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_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.

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_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 Types

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.

##### 2.4.3.2 Setup

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.

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.

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 array in bytes. This must be between 2 and 2147483648 inclusive, and is rounded up to the next power of 2 internally. Note that one byte 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_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.

##### 2.4.3.3 Reading

Functions that may only be called by the read thread.

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_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_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.4 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.

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`.

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_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`.

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.

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.

#### 2.4.4 Tree

##### 2.4.4.1 Types

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.

##### 2.4.4.2 Setup

void zix_tree_free(ZixTree *t)

  Free `t`.

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.

size_t zix_tree_size(const ZixTree *t)

  Return the number of elements in `t`.

##### 2.4.4.3 Iteration

typedef struct ZixTreeNodeImpl ZixTreeIter

  An iterator over a `ZixTree`.

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`.

void *zix_tree_get(const ZixTreeIter *ti)

  Return the data associated with the given tree item.

bool zix_tree_iter_is_end(const ZixTreeIter *i)

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

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`.

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`.

##### 2.4.4.4 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.5 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 (compared to 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_destroy(ZixSem *sem)

  Destroy `sem`.

  Returns:

    `ZixStatus.ZIX_STATUS_SUCCESS`, or an error.

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_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_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.

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_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.

#### 2.5.2 Thread

ZIX_THREAD_FUNC

  Thread function attribute.

ZIX_THREAD_RESULT

  NULL

  Result returned from a thread function.

typedef pthread_t ZixThread

  A thread.

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.

typedef void *ZixThreadResult

  Thread function return type.

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`.

### 2.6 File System

#### 2.6.1 Operations

##### 2.6.1.1 Creation and Removal

typedef uint32_t ZixCopyOptions

  Bitwise OR of `ZixCopyOptions` values.

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.

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_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.

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_directory_symlink(const char *target_path, const char *link_path)

  Create a symbolic link at `link_path` that points to `target_path`.

  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.

ZixStatus zix_create_hard_link(const char *target_path, const char *link_path)

  Create a hard link at `link_path` that points to `target_path`.

  Returns:

    `ZixStatus.ZIX_STATUS_SUCCESS`, or an error.

ZixStatus zix_create_symlink(const char *target_path, const char *link_path)

  Create a symbolic link at `link_path` that points to `target_path`.

  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.

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

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.

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.

  enumerator ZIX_FILE_TYPE_SYMLINK

    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.

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.

ZixFileType zix_file_type(const char *path)

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

ZixFileType zix_symlink_type(const char *path)

  Return the type of a file or directory or symlink.

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

##### 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_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 “/” everywhere 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).

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).

##### 2.6.2.3 Decomposition

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.

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_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:

    A view of the parent path within `path`, or the empty string if it has no parent.

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_root_directory(const char *path)

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

ZixStringView zix_path_root_name(const char *path)

  Return the root name of `path` like “C:”, 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:

    A view of the root path within `path`, or an empty string if it has no root.

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.

##### 2.6.2.4 Queries

bool zix_path_has_extension(const char *path)

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

bool zix_path_has_filename(const char *path)

  Return true if `path` has a filename like “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_relative_path(const char *path)

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

bool zix_path_has_root_directory(const char *path)

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

bool zix_path_has_root_name(const char *path)

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

bool zix_path_has_root_path(const char *path)

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

bool zix_path_has_stem(const char *path)

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

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.